Skip to content

[Resources] Add Publish-AzBicepModule for publishing Bicep files #16159

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

Merged
merged 14 commits into from
Nov 11, 2021
Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// ----------------------------------------------------------------------------------
//
// 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.ResourceManager.Cmdlets.Utilities;
using Microsoft.Azure.Commands.ResourceManager.Common;
using Microsoft.WindowsAzure.Commands.Utilities.Common;
using System.Management.Automation;

namespace Microsoft.Azure.Commands.ResourceManager.Cmdlets.Implementation.Bicep
{
[Cmdlet(VerbsData.Publish, AzureRMConstants.AzureRMPrefix + "BicepModule", SupportsShouldProcess = true), OutputType(typeof(bool))]
public class PublishAzureBicepModuleCmdlet : AzureRMCmdlet
{
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "Local path to the bicep file to publish.")]
[ValidateNotNullOrEmpty]
public string FilePath { get; set; }

[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, HelpMessage = "The target location where the bicep file will be published.")]
[ValidateNotNullOrEmpty]
public string Target { get; set; }

[Parameter(Mandatory = false, HelpMessage="Indicates that this cmdlet returns a boolean result. By default, this cmdlet does not generate any output.")]
public SwitchParameter PassThru { get; set; }

public override void ExecuteCmdlet()
{
BicepUtility.PublishFile(this.TryResolvePath(this.FilePath), this.Target, this.WriteVerbose, this.WriteWarning);

if (this.PassThru.IsPresent)
{
this.WriteObject(true);
}
}
}
}
180 changes: 100 additions & 80 deletions src/Resources/ResourceManager/Utilities/BicepUtility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,132 +12,152 @@
// 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;
using System.Text.RegularExpressions;

namespace Microsoft.Azure.Commands.ResourceManager.Cmdlets.Utilities
{
using Microsoft.Azure.Commands.Common.Authentication.Abstractions;
using Microsoft.Azure.Commands.Common.Exceptions;
using Microsoft.WindowsAzure.Commands.Utilities.Common;
using System;
using System.IO;
using System.Management.Automation;
using System.Text.RegularExpressions;

internal static class BicepUtility
{
public static bool IsBicepExecutable { get; private set; } = false;
private static bool IsBicepExecutable = false;

private const string MinimalVersionRequirement = "0.3.1";

private const string MinimalVersionRequirementForBicepPublish = "0.4.1008";

public static string MinimalVersionRequirement { get; private set; } = "0.3.1";
public delegate void OutputCallback(string msg);

public static bool IsBicepFile(string templateFilePath)
public static bool IsBicepFile(string templateFilePath) =>
".bicep".Equals(Path.GetExtension(templateFilePath), StringComparison.OrdinalIgnoreCase);

public static string BuildFile(string bicepTemplateFilePath, OutputCallback writeVerbose = null, OutputCallback writeWarning = null)
{
return ".bicep".Equals(Path.GetExtension(templateFilePath), System.StringComparison.OrdinalIgnoreCase);
if (!FileUtilities.DataStore.FileExists(bicepTemplateFilePath))
{
throw new AzPSArgumentException(Properties.Resources.InvalidBicepFilePath, "TemplateFile");
}

string tempDirectory = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
Directory.CreateDirectory(tempDirectory);

RunBicepCommand($"bicep build '{bicepTemplateFilePath}' --outdir '{tempDirectory}'", MinimalVersionRequirement, writeVerbose, writeWarning);

string buildResultPath = Path.Combine(tempDirectory, Path.GetFileName(bicepTemplateFilePath)).Replace(".bicep", ".json");
if (!FileUtilities.DataStore.FileExists(buildResultPath))
{
throw new AzPSApplicationException(string.Format(Properties.Resources.BuildBicepFileToJsonFailed, bicepTemplateFilePath));
}

return buildResultPath;
}

public static bool CheckBicepExecutable()
public static void PublishFile(string bicepFilePath, string target, OutputCallback writeVerbose = null, OutputCallback writeWarning = null)
{
System.Management.Automation.PowerShell powershell = System.Management.Automation.PowerShell.Create();
powershell.AddScript("Get-Command bicep");
powershell.Invoke();
powershell.AddScript("$?");
var result = powershell.Invoke();
bool.TryParse(result[0].ToString(), out bool res);
// Cache result
IsBicepExecutable = res;
return IsBicepExecutable;
if (!FileUtilities.DataStore.FileExists(bicepFilePath))
{
throw new AzPSArgumentException(Properties.Resources.InvalidBicepFilePath, "File");
}

RunBicepCommand($"bicep publish '{bicepFilePath}' --target '{target}'", MinimalVersionRequirementForBicepPublish, writeVerbose, writeWarning);
}

private static void CheckBicepExecutable()
{
using (var powerShell = PowerShell.Create())
{
if (IsBicepExecutable)
{
return;
}

powerShell.AddScript("Get-Command bicep");
powerShell.Invoke();
powerShell.AddScript("$?");
var result = powerShell.Invoke();
// Cache result
bool.TryParse(result[0].ToString(), out IsBicepExecutable);

if (!IsBicepExecutable)
{
throw new AzPSApplicationException(Properties.Resources.BicepNotFound);
}
}
}

private static string CheckMinimalVersionRequirement(string minimalVersionRequirement)
{
string currentBicepVersion = GetBicepVesion();
string currentBicepVersion = GetBicepVersion();
if (Version.Parse(minimalVersionRequirement).CompareTo(Version.Parse(currentBicepVersion)) > 0)
{
throw new AzPSApplicationException(string.Format(Properties.Resources.BicepVersionRequirement, minimalVersionRequirement));
};
return currentBicepVersion;
}

public static string GetBicepVesion()
private static string GetBicepVersion()
{
using(System.Management.Automation.PowerShell powershell = System.Management.Automation.PowerShell.Create())
using (var powerShell = PowerShell.Create())
{
powershell.AddScript("bicep -v");
var result = powershell.Invoke()[0].ToString();
powerShell.AddScript("bicep -v");
var result = powerShell.Invoke()[0].ToString();
Regex pattern = new Regex("\\d+(\\.\\d+)+");
string bicepVersion = pattern.Match(result)?.Value;

return bicepVersion;
}
}

public static int GetLastExitCode(System.Management.Automation.PowerShell powershell)
private static int GetLastExitCode(PowerShell powershell)
{
powershell.AddScript("$LASTEXITCODE");
var result = powershell.Invoke();
int.TryParse(result[0]?.ToString(), out int exitcode);
return exitcode;
}

public delegate void VerboseOutputMethod(string msg);
public delegate void WarningOutputMethod(string msg);

public static string BuildFile(string bicepTemplateFilePath, VerboseOutputMethod writeVerbose = null, WarningOutputMethod writeWarning = null)
private static void RunBicepCommand(string command, string minimalVersionRequirement, OutputCallback writeVerbose = null, OutputCallback writeWarning = null)
{
if (!IsBicepExecutable && !CheckBicepExecutable())
{
throw new AzPSApplicationException(Properties.Resources.BicepNotFound);
}
CheckBicepExecutable();

string currentBicepVersion = CheckMinimalVersionRequirement(MinimalVersionRequirement);
string currentBicepVersion = CheckMinimalVersionRequirement(minimalVersionRequirement);

string tempDirectory = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
Directory.CreateDirectory(tempDirectory);

if (FileUtilities.DataStore.FileExists(bicepTemplateFilePath))
using (var powerShell = PowerShell.Create())
{
using (System.Management.Automation.PowerShell powershell = System.Management.Automation.PowerShell.Create())
powerShell.AddScript(command);
var result = powerShell.Invoke();

if (writeVerbose != null)
{
powershell.AddScript($"bicep build '{bicepTemplateFilePath}' --outdir '{tempDirectory}'");
var result = powershell.Invoke();

if (writeVerbose != null)
{
writeVerbose(string.Format("Using Bicep v{0}", currentBicepVersion));
result.ForEach(r => writeVerbose(r.ToString()));
}
writeVerbose(string.Format("Using Bicep v{0}", currentBicepVersion));
result.ForEach(r => writeVerbose(r.ToString()));
}

// Bicep uses error stream to report warning message and error message, record it
string warningOrErrorMsg = string.Empty;
if (powershell.HadErrors)
{
powershell.Streams.Error.ForEach(e => { warningOrErrorMsg += (e + Environment.NewLine); });
warningOrErrorMsg = warningOrErrorMsg.Substring(0, warningOrErrorMsg.Length - Environment.NewLine.Length);
}
// Bicep uses error stream to report warning message and error message, record it
string warningOrErrorMsg = string.Empty;
if (powerShell.HadErrors)
{
powerShell.Streams.Error.ForEach(e => { warningOrErrorMsg += (e + Environment.NewLine); });
warningOrErrorMsg = warningOrErrorMsg.Substring(0, warningOrErrorMsg.Length - Environment.NewLine.Length);
}

if (0 == GetLastExitCode(powershell))
{
// print warning message
if(writeWarning != null && !string.IsNullOrEmpty(warningOrErrorMsg))
{
writeWarning(warningOrErrorMsg);
}
}
else
if (0 == GetLastExitCode(powerShell))
{
// print warning message
if (writeWarning != null && !string.IsNullOrEmpty(warningOrErrorMsg))
{
throw new AzPSApplicationException(warningOrErrorMsg);
writeWarning(warningOrErrorMsg);
}
}
else
{
throw new AzPSApplicationException(warningOrErrorMsg);
}
}
else
{
throw new AzPSArgumentException(Properties.Resources.InvalidBicepFilePath, "TemplateFile");
}

string buildResultPath = Path.Combine(tempDirectory, Path.GetFileName(bicepTemplateFilePath)).Replace(".bicep", ".json");
if (!FileUtilities.DataStore.FileExists(buildResultPath))
{
throw new AzPSApplicationException(string.Format(Properties.Resources.BuildBicepFileToJsonFailed, bicepTemplateFilePath));
}
return buildResultPath;
}
}
}
}
3 changes: 2 additions & 1 deletion src/Resources/Resources/Az.Resources.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,8 @@ CmdletsToExport = 'Get-AzProviderOperation', 'Remove-AzRoleAssignment',
'Get-AzManagementGroupDeploymentWhatIfResult',
'Get-AzTenantDeploymentWhatIfResult', 'Get-AzTemplateSpec',
'New-AzTemplateSpec', 'Set-AzTemplateSpec', 'Export-AzTemplateSpec',
'Remove-AzTemplateSpec'
'Remove-AzTemplateSpec',
'Publish-AzBicepModule'

# Variables to export from this module
# VariablesToExport = @()
Expand Down
1 change: 1 addition & 0 deletions src/Resources/Resources/ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
-->

## Upcoming Release
* Added new cmdlet `Publish-AzBicepModule` for publishing Bicep modules

## Version 4.4.1
* Fixed a bug about the exitcode of Bicep [#16055]
Expand Down
3 changes: 3 additions & 0 deletions src/Resources/Resources/help/Az.Resources.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,9 @@ Creates a new Template Spec.
### [New-AzTenantDeployment](New-AzTenantDeployment.md)
Create a deployment at tenant scope

### [Publish-AzBicepModule](Publish-AzBicepModule.md)
Publishes a Bicep file to a registry.

### [Register-AzProviderFeature](Register-AzProviderFeature.md)
Registers an Azure provider feature in your account.

Expand Down
Loading