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

Added support for bicep files in AzureResourceManagerTemplateDeploymentV3 #15788

Merged
merged 21 commits into from
Feb 1, 2022
Merged
Show file tree
Hide file tree
Changes from 13 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 Tasks/AzureResourceManagerTemplateDeploymentV3/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ The parameters of the task are described in details, including examples, to show
- For Resource Group deployment scope: Location for deploying the resource group. If the resource group already exists in the subscription, then this value will be ignored.
- For other deployment scopes: Location for storing the deployment metadata.

* **Template location**: The location of the Template & the Parameters JSON files. Select "Linked Artifact" if the files are part of the linked code/build artifacts. Select "URL of the file" if the JSON files are located at any publicly accessible http/https URLs. To use a file stored in a private storage account, retrieve and include the shared access signature (SAS) token in the URL of the template. Example: <blob_storage_url>/template.json?<SAStoken>. To upload a parameters file to a storage account and generate a SAS token, you could use [Azure file copy task](https://aka.ms/azurefilecopyreadme) or follow the steps using [PowerShell](https://go.microsoft.com/fwlink/?linkid=838080) or [Azure CLI](https://go.microsoft.com/fwlink/?linkid=836911).
* **Template location**: The location of the Template & the Parameters JSON files. Select "Linked Artifact" if the files are part of the linked code/build artifacts. For "Linked Artifacts", you can also specify the path to a Bicep file. Select "URL of the file" if the JSON files are located at any publicly accessible http/https URLs. To use a file stored in a private storage account, retrieve and include the shared access signature (SAS) token in the URL of the template. Example: <blob_storage_url>/template.json?<SAStoken>. To upload a parameters file to a storage account and generate a SAS token, you could use [Azure file copy task](https://aka.ms/azurefilecopyreadme) or follow the steps using [PowerShell](https://go.microsoft.com/fwlink/?linkid=838080) or [Azure CLI](https://go.microsoft.com/fwlink/?linkid=836911).

* **Template and its Parameters**: The templates and the templates parameters file are the Azure templates available at [GitHub](https://github.com/Azure/azure-quickstart-templates) or in the [Azure gallery](https://azure.microsoft.com/en-in/documentation/articles/powershell-azure-resource-manager/). To get started immediately use [this](https://aka.ms/sampletemplate) template that is available on GitHub.
- These files can be either be located at any publicly accessible http/https URLs or be in a checked in the Version Control or they can be part of the build itself. If the files are part of the Build, use the pre-defined [system variables](https://msdn.microsoft.com/Library/vs/alm/Build/scripts/variables) provided by the Build to specify their location. The variables to use are $(Build.Repository.LocalPath), if the templates are checked-in but are not built, or $(Agent.BuildDirectory), if the templates are built as part of the solution. Be sure to specify the full path like $(Build.Repository.LocalPath)\Azure Templates\AzureRGDeploy.json. Wildcards like \*\*\\\*.json or \*\*\\*.param.json are also supported and there needs to be only one file that matches the search pattern at the location. If more than one file matches the search pattern, then the task will error out.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
param location string = 'eastasia'

var storageAccountName_var = 'deepak2121'
var storageAccountType = 'Premium_LRS'

resource storageAccount 'Microsoft.Storage/storageAccounts@2021-01-01' = {
name: toLower(take(storageAccountName_var, 24))
location: location
sku: {
name: storageAccountType
}
kind: 'StorageV2'
}

output storageAccount_Name string = storageAccount.name
output storageAccount_Location string = storageAccount.location
output storageAccount_SKUName string = storageAccount.sku.name
output storageAccount_Kind string = storageAccount.kind
20 changes: 20 additions & 0 deletions Tasks/AzureResourceManagerTemplateDeploymentV3/Tests/L0.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,4 +172,24 @@ describe('Azure Resource Manager Template Deployment', function () {
done(error);
}
});

it('Successfully triggered createOrUpdate deployment using bicep file', (done) => {
let tp = path.join(__dirname, 'createOrUpdate.js');
process.env["csmFile"] = "CSMwithBicep.bicep";
process.env["csmParametersFile"] = "";
process.env["deploymentOutputs"] = "someVar";
let tr = new ttm.MockTestRunner(tp);
tr.run();
try {
assert(tr.succeeded, "Should have succeeded");
assert(tr.stdout.indexOf("deployments.createOrUpdate is called") > 0, "deployments.createOrUpdate function should have been called from azure-sdk");
assert(tr.stdout.indexOf("##vso[task.setvariable variable=someVar;]") >= 0, "deploymentsOutput should have been updated");
done();
}
catch (error) {
console.log("STDERR", tr.stderr);
console.log("STDOUT", tr.stdout);
done(error);
}
t-dedah marked this conversation as resolved.
Show resolved Hide resolved
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@ process.env["ENDPOINT_DATA_AzureRM_ENVIRONMENTAUTHORITYURL"] = "https://login.wi
process.env["ENDPOINT_DATA_AzureRM_ACTIVEDIRECTORYSERVICEENDPOINTRESOURCEID"] = "https://management.azure.com";

var CSMJson = path.join(__dirname, "CSM.json");
var CSMBicep = path.join(__dirname, "CSMwithBicep.bicep");
var CSMwithComments = path.join(__dirname, "CSMwithComments.json");
var defaults = path.join(__dirname, "defaults.json");
var faultyCSM = path.join(__dirname, "faultyCSM.json");

let a: ma.TaskLibAnswers = <ma.TaskLibAnswers>{
"findMatch": {
"CSM.json": [CSMJson],
"CSMwithBicep.bicep": [CSMBicep],
"CSMwithComments.json": [CSMwithComments],
"defaults.json": [defaults],
"faultyCSM.json": [faultyCSM],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@ export class DeploymentScopeBase {

public async deploy(): Promise<void> {
await this.createTemplateDeployment();
utils.deleteGeneratedFiles()
}

protected async createTemplateDeployment() {
console.log(tl.loc("CreatingTemplateDeployment"));
var params: DeploymentParameters;
if (this.taskParameters.templateLocation === "Linked artifact") {
params = utils.getDeploymentDataForLinkedArtifact(this.taskParameters);
params = await utils.getDeploymentDataForLinkedArtifact(this.taskParameters);
} else if (this.taskParameters.templateLocation === "URL of the file") {
params = await utils.getDeploymentObjectForPublicURL(this.taskParameters);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export class ResourceGroup extends DeploymentScopeBase {
public async deploy(): Promise<void> {
await this.createResourceGroupIfRequired();
await this.createTemplateDeployment();
utils.deleteGeneratedFiles()
}

public deleteResourceGroup(): Promise<void> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { TemplateObject, ParameterValue } from "../models/Types";
import httpInterfaces = require("typed-rest-client/Interfaces");
import { DeploymentParameters } from "./DeploymentParameters";

var cpExec = util.promisify(require('child_process').exec);
var hm = require("typed-rest-client/HttpClient");
var uuid = require("uuid");

Expand All @@ -33,6 +34,8 @@ function formatNumber(num: number): string {
}

class Utils {
public static cleanupFileList = []

public static isNonEmpty(str: string): boolean {
return (!!str && !!str.trim());
}
Expand Down Expand Up @@ -222,7 +225,7 @@ class Utils {
return deploymentName;
}

public static getDeploymentDataForLinkedArtifact(taskParameters: armDeployTaskParameters.TaskParameters): DeploymentParameters {
public static async getDeploymentDataForLinkedArtifact(taskParameters: armDeployTaskParameters.TaskParameters): Promise<DeploymentParameters> {
var template: TemplateObject;
var fileMatches = tl.findMatch(tl.getVariable("System.DefaultWorkingDirectory"), this.escapeBlockCharacters(taskParameters.csmFile));
if (fileMatches.length > 1) {
Expand All @@ -234,6 +237,7 @@ class Utils {
var csmFilePath = fileMatches[0];
if (!fs.lstatSync(csmFilePath).isDirectory()) {
tl.debug("Loading CSM Template File.. " + csmFilePath);
csmFilePath = await this.getFilePathForLinkedArtifact(csmFilePath)
try {
template = JSON.parse(this.stripJsonComments(fileEncoding.readFileContentsAsText(csmFilePath)));
}
Expand All @@ -257,6 +261,7 @@ class Utils {
var csmParametersFilePath = fileMatches[0];
if (!fs.lstatSync(csmParametersFilePath).isDirectory()) {
tl.debug("Loading Parameters File.. " + csmParametersFilePath);
csmParametersFilePath = await this.getFilePathForLinkedArtifact(csmParametersFilePath)
try {
var parameterFile = JSON.parse(this.stripJsonComments(fileEncoding.readFileContentsAsText(csmParametersFilePath)));
tl.debug("Loaded Parameters File");
Expand Down Expand Up @@ -285,6 +290,16 @@ class Utils {
return deploymentParameters;
}

public static deleteGeneratedFiles(): void{
this.cleanupFileList.forEach(filePath => {
try{
fs.unlinkSync(filePath);
}catch(err){
console.log(tl.loc("BicepFileCleanupFailed", err))
}
});
}

private static getPolicyHelpLink(taskParameters: armDeployTaskParameters.TaskParameters, errorDetail) {
var additionalInfo = errorDetail.additionalInfo;
if (!!additionalInfo) {
Expand Down Expand Up @@ -407,6 +422,62 @@ class Utils {
private static escapeBlockCharacters(str: string): string {
return str.replace(/[\[]/g, '$&[]');
}

private static async getFilePathForLinkedArtifact(filePath: string): Promise<string> {
var filePathExtension: string = filePath.split('.').pop();
if(filePathExtension === 'bicep'){
let azcliversion = await this.getAzureCliVersion()
if(parseFloat(azcliversion)){
if(this.isBicepAvailable(azcliversion)){
await this.execBicepBuild(filePath)
filePath = filePath.replace('.bicep', '.json')
this.cleanupFileList.push(filePath)
}else{
throw new Error(tl.loc("IncompatibleAzureCLIVersion"));
}
}else{
throw new Error(tl.loc("AzureCLINotFound"));
}
}
return filePath
}

private static async getAzureCliVersion(): Promise<string> {
let azcliversion: string = "" ;
try {
const { stdout, stderr } = await cpExec('az version');
if (!stderr) {
azcliversion = JSON.parse(stdout)["azure-cli"]
} else {
throw stderr
}
} catch (err) {
throw new Error(tl.loc("FailedToFetchAzureCLIVersion", err));
}
return azcliversion
}

private static async execBicepBuild(filePath): Promise<void> {
try {
const { stdout, stderr } = await cpExec(`az bicep build --file ${filePath}`);
t-dedah marked this conversation as resolved.
Show resolved Hide resolved
if (!stderr) {
} else {
throw stderr
}
} catch (err) {
throw new Error(tl.loc("BicepBuildFailed", err));
}
}

private static isBicepAvailable(azcliversion): Boolean{
let majorVersion = azcliversion.split('.')[0]
let minorVersion = azcliversion.split('.')[1]
// Support Bicep was introduced in az-cli 2.20.0
if((majorVersion == 2 && minorVersion >= 20) || majorVersion > 2){
return true
}
return false
}
}

export = Utils;
13 changes: 9 additions & 4 deletions Tasks/AzureResourceManagerTemplateDeploymentV3/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"author": "Microsoft Corporation",
"version": {
"Major": 3,
"Minor": 198,
"Minor": 199,
"Patch": 0
},
"demands": [],
Expand Down Expand Up @@ -161,7 +161,7 @@
"required": true,
"groupName": "Template",
"visibleRule": " templateLocation = Linked artifact",
"helpMarkDown": "Specify the path or a pattern pointing to the Azure Resource Manager template. For more information about the templates see https://aka.ms/azuretemplates. To get started immediately use template https://aka.ms/sampletemplate."
"helpMarkDown": "Specify the path or a pattern pointing to the Azure Resource Manager template. For more information about the templates see https://aka.ms/azuretemplates. To get started immediately use template https://aka.ms/sampletemplate. 'Linked artifact' also has support for Bicep files when the Azure CLI version > 2.20.0"
},
{
"name": "csmParametersFile",
Expand All @@ -170,7 +170,7 @@
"defaultValue": "",
"required": false,
"groupName": "Template",
"helpMarkDown": "Specify the path or a pattern pointing for the parameters file for the Azure Resource Manager template.",
"helpMarkDown": "Specify the path or a pattern pointing for the parameters file for the Azure Resource Manager template. 'Linked artifact' also has support for Bicep files when the Azure CLI version > 2.20.0",
"visibleRule": " templateLocation = Linked artifact"
},
{
Expand Down Expand Up @@ -309,6 +309,11 @@
"ManagedServiceIdentityDetails": "Please make sure the Managed Service Identity used for deployment is assigned the right roles for the Resource Group %s. Follow the link for more details: https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/howto-assign-access-portal",
"CompleteDeploymentModeNotSupported": "Deployment mode 'Complete' is not supported for deployment at '%s' scope",
"TemplateValidationFailure": "Validation errors were found in the Azure Resource Manager template. This can potentially cause template deployment to fail. %s. Please follow https://docs.microsoft.com/en-us/azure/azure-resource-manager/templates/template-syntax",
"TroubleshootingGuide": "Check out the troubleshooting guide to see if your issue is addressed: https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/deploy/azure-resource-group-deployment?view=azure-devops#troubleshooting"
"TroubleshootingGuide": "Check out the troubleshooting guide to see if your issue is addressed: https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/deploy/azure-resource-group-deployment?view=azure-devops#troubleshooting",
"IncompatibleAzureCLIVersion": "Azure CLI version should be >= 2.20.0",
"AzureCLINotFound": "Azure CLI not found on the agent.",
"FailedToFetchAzureCLIVersion": "Failed to fetch az cli version from agent. Error: %s",
"BicepBuildFailed": "\"az bicep build\" failed. Error: %s",
"BicepFileCleanupFailed": "Failed to delete Bicep file. Error: %s"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"author": "Microsoft Corporation",
"version": {
"Major": 3,
"Minor": 198,
"Minor": 199,
"Patch": 0
},
"demands": [],
Expand Down Expand Up @@ -309,6 +309,11 @@
"ManagedServiceIdentityDetails": "ms-resource:loc.messages.ManagedServiceIdentityDetails",
"CompleteDeploymentModeNotSupported": "ms-resource:loc.messages.CompleteDeploymentModeNotSupported",
"TemplateValidationFailure": "ms-resource:loc.messages.TemplateValidationFailure",
"TroubleshootingGuide": "ms-resource:loc.messages.TroubleshootingGuide"
"TroubleshootingGuide": "ms-resource:loc.messages.TroubleshootingGuide",
"IncompatibleAzureCLIVersion": "ms-resource:loc.messages.IncompatibleAzureCLIVersion",
"AzureCLINotFound": "ms-resource:loc.messages.AzureCLINotFound",
"FailedToFetchAzureCLIVersion": "ms-resource:loc.messages.FailedToFetchAzureCLIVersion",
"BicepBuildFailed": "ms-resource:loc.messages.BicepBuildFailed",
"BicepFileCleanupFailed": "ms-resource:loc.messages.BicepFileCleanupFailed"
}
}