diff --git a/CHANGELOG.md b/CHANGELOG.md index 606d487..3e015d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,15 @@ -## 0.2.2 -- Bug fixes -- Removed animation +## 0.3.0 +- Bug fixes, the usual; variable resolution and linked templates +- Much improved handling of multi-level linked templates (links within links) +- New layout modes; tree and grid +- Animation removed in most situations, it was annoying +- Support for multi-line strings (I think!) + -- ## 0.2.1 +## 0.2.1 - Bug fixes + ## 0.2.0 - Support for loading a parameters file and applying values to the output - Filter out resources by type, helps de-clutter the view on very busy templates, or when you have many similar resources you want to hide (e.g. NSG rules) @@ -16,33 +21,40 @@ - Many new icons; automation, SQL Server, blobservices, and more - A bunch more bugs I expect ;) + ## 0.1.1 - Several fixes & improvements to how linked templates are handled and searched for + ## 0.1.0 - Support for linked & nested templates! See readme for limitations - Support for the new vscode-azurearmtools extension language server and 'ARM Template' language type - displayName tag if present will be used in place of the resource name + ## 0.0.9 - Many fixes to parameter & variable resolution - Improved error messages and logging - Tested successfully against ALL templates on https://github.com/Azure/azure-quickstart-templates + ## 0.0.8 - Parameter & variable values which are objects now resolved - Parameters defaultValues detected picked up and used - Display unresolvable properties in italics and inside {} + ## 0.0.7 - Many, many new icons added! - Support for JSON comments, which is allowed by ARM - Custom default icon for API Management sub-resources + ## 0.0.6 - Fix for SKU evaluation error (exp.trim) - Nicer error messages - + + ## 0.0.5 - Snap to grid added - Telemetry tracking added @@ -50,14 +62,17 @@ - Added resource SKU details to info box - Smarter handling of updates when user is editing JSON + ## 0.0.4 - Fixed initialization & first display problems - Added preview icon to top right of editor menu bar - More robust activation options/filters - Extension is now a singleton panel + ## 0.0.3 - Super minor readme fixes, added this changelog + ## 0.0.2 - Initial release. We don't talk about v0.0.1 diff --git a/README.md b/README.md index 1c46ee2..07ed6fd 100644 --- a/README.md +++ b/README.md @@ -26,11 +26,18 @@ Extension as been tested successfully against all 890+ [Azure Quickstart Templat - Use keyboard shortcut `Ctrl+Alt+Q` ## Basic Features -- Click on a resource to show popup 'infobox' for that resource +- Click on a resource to show popup 'infobox' for that resource, a selected subset of details will be shown +- Click and drag on background to move and pan the view around +- Zoom in and out with the mouse wheel +- Drag icons around to layout as your wish, one tip is to click 'Re-fit' after moving to get the best view & zoom level + +## Toolbar - Click the 'Labels' toolbar button to toggle labels from resource names to resource types - Click the 'Re-fit' toolbar button to refit the view to the best zoom level - Click the 'Snap' toolbar button to toggle snap to grid mode on/off -- Click the 'Layout' toolbar button to re-layout icons in default +- Two auto layout modes are available: + - 'Tree' lays out the nodes in a hierarchical manner, ok for small templates, also the default + - 'Grid' puts the nodes on a grid, better for large templates but will often not make logical sense ## Parameter Files By default the extension will try to use any `defaultValues` found in the parameters section of the template. @@ -49,7 +56,7 @@ The extension will attempt to locate and display linked templates, these resourc - If the resolved linked template URL is externally accessible, it will be downloaded and used. - If the URL is not accessible, then an attempt is made to load the file locally based on a guess from the filename and parent dir extracted from the URL, e.g. `nested/linked.json` - If that fails, then the local filesystem of the VS Code workspace will be searched for the file. Some assumptions are made in this search: - - The search will only happen if the linked file has a *different* filename from the main/master template being viewed + - The search will only happen if the linked file has a *different* filename from the main/master template being viewed. Otherwise the search would just find the main template being viewed - The linked template file should located somewhere under the path of the main template, sub-folders will be searched. If the file resides elsewhere outside this path it will not be located. - The first matching file will be used - If linked template URL or filename is dynamic based on template parameters it is very likely not to resolve, and will not be found. @@ -62,16 +69,15 @@ This is a port of a older *ARM Viewer* project, which was a standalone Node.js w This project was created as a learning exercise, but was heavily inspired & influenced by the old ARMViz tool. ARMViz sadly seems to have been abandoned, it often has problems displaying some templates. Personally I wasn't a fan of look of the output, and found it hard to read. These are a few of the reasons why I created this project +## ARM Template JSON Support +ARM templates go outside the JSON specification and break it in a couple of areas: +- Support for comments in the JSON file (aka JSONC) +- Allowing the use of multi-line strings +The extension supports both of these as far as is reasonably possible, multi-line strings in particular has no known spec on how it should be supported. The extension is also aware of the language server provided by the 'Azure Resource Manager Tools' extension and will accept files set to 'arm-template' as the language type. + -# Limitations & Known Issues +## Limitations & Known Issues - The code attempts to find the links (`dependsOn` relationships) between ARM resources, however due to the *many* subtle and complex ways these relationships can be defined & expressed, certain links may not be picked up & displayed. - Icons for the most commonly used & popular resource types have been added, however not every resource is covered (There's simply too many and no canonical source). The default ARM cube icon will be shown as a fallback. Get in touch if you want a icon added for a particular resource type. - Resolving names & other properties for resources is attempted, but due to programmatic way these are generally defined with ARM functions and expressions, full name resolution is not always possible - Templates using the loop functions `copy` & `copyIndex` to create multiple resources will not be rendered correctly due to limitations on evaluating the dynamic iterative state of the template - - -# Running/Debugging Locally -- Clone/fork repo and open project in VS Code 1.25+ -- `npm install` -- `npm run watch` or `npm run compile` -- `F5` to start debugging diff --git a/assets/css/main.css b/assets/css/main.css index 4051806..9ee7bae 100644 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -56,7 +56,7 @@ button { margin: 2px; border-radius: 3px; font-size: 95%; - min-width: 70px; + /* min-width: 70px; */ text-align: center; } diff --git a/assets/img/toolbar/cose.svg b/assets/img/toolbar/cose.svg new file mode 100644 index 0000000..2f58746 --- /dev/null +++ b/assets/img/toolbar/cose.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/img/toolbar/grid.svg b/assets/img/toolbar/grid.svg new file mode 100644 index 0000000..eabb1e9 --- /dev/null +++ b/assets/img/toolbar/grid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/img/toolbar/tree.svg b/assets/img/toolbar/tree.svg new file mode 100644 index 0000000..0962b4d --- /dev/null +++ b/assets/img/toolbar/tree.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/js/main.js b/assets/js/main.js index ac1e6ed..78e985a 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -83,6 +83,8 @@ function init(prefix) { // function displayData(data) { console.log("### ArmView: Displaying received data"); + //console.dir(JSON.stringify(data, null, 3)); + cy.remove('*'); cy.add(data); @@ -99,7 +101,7 @@ function displayData(data) { } } - reLayout(); + reLayout('breadthfirst', false); } // @@ -113,20 +115,17 @@ function _addInfo(name, value) { table = document.getElementById('infotable'); - if(value.startsWith('http')) { - table.insertAdjacentHTML('beforeend', `${_utilTitleCase(name)}${value}`) - } else { - let valClass = ''; - if(value.startsWith('{') && value.endsWith('}')) - valClass = 'italic'; - table.insertAdjacentHTML('beforeend', `${_utilTitleCase(name)}${value}`); - } + let valClass = ''; + if(value.startsWith('{') && value.endsWith('}')) + valClass = 'italic'; + + table.insertAdjacentHTML('beforeend', `${_utilTitleCase(name)}${value}`); } // // Layout the view of nodes given current data // -function reLayout() { +function reLayout(mode, animate) { // Set colors in keeping with VS code theme (might be dark or light) let bgColor = window.getComputedStyle(document.getElementsByTagName('body')[0]).getPropertyValue('background-color'); let textColor = '#eeeeee'; @@ -180,7 +179,7 @@ function reLayout() { // Bounding box for groups cy.style().selector(':parent').style({ 'background-image': null, - 'label': node => { return decodeURIComponent(node.data(labelField)) }, + 'label': node => { return getLabel(node) }, //decodeURIComponent(node.data(labelField)) }, 'border-width': '4', 'border-color': '#000', 'border-opacity': 0.5, @@ -204,14 +203,24 @@ function reLayout() { else cy.snapToGrid('snapOff'); - // Re-layout nodes in breadthfirst mode, resizing and fitting too + // Re-layout nodes in given mode, resizing and fitting too cy.style().update() cy.resize(); + + if(!mode) mode = 'breadthfirst' + if(!animate) animate = false + let spacing = 1.5 + if(mode == 'grid') + spacing = 1.75 + cy.layout({ - name: 'breadthfirst', - nodeDimensionsIncludeLabels: false - //animate: true + avoidOverlap: true, + name: mode, + nodeDimensionsIncludeLabels: false, + spacingFactor: spacing, + animate: animate }).run(); + cy.fit(); } @@ -221,7 +230,7 @@ function reLayout() { function toggleLabels() { labelField = labelField == 'label' ? 'name' : 'label' cy.style().selector('node').style({ - 'label': node => { return decodeURIComponent(node.data(labelField)) }, + 'label': node => { return getLabel(node) } //decodeURIComponent(node.data(labelField)) }, }).update(); } @@ -356,14 +365,10 @@ function sendMessage(msg) { // // Get label for resource // -// function getLabel(node) { -// // Special case - if resource has displayName tag -// if(labelField == 'name') { -// for(let extraField in node.data('extra')) { -// if(extraField.toLowerCase() == 'tag displayname') { -// return decodeURIComponent(node.data('extra')['tag displayname']); -// } -// } -// } -// return decodeURIComponent(node.data(labelField)); -// } \ No newline at end of file +function getLabel(node) { + let label = decodeURIComponent(node.data(labelField)); + if(label.length > 24) { + label = label.substr(0, 24) + "…" + } + return label; +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 1c63d45..10b1fea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "armview", - "version": "0.2.2", + "version": "0.3.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -749,11 +749,6 @@ } } }, - "jsonc-parser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-2.1.1.tgz", - "integrity": "sha512-VC0CjnWJylKB1iov4u76/W/5Ef0ydDkjtYWxoZ9t3HdWlSnZQwZL5MgFikaB/EtQ4RmMEw3tmQzuYnZA2/Ja1g==" - }, "jsonlint": { "version": "1.6.3", "resolved": "https://registry.npmjs.org/jsonlint/-/jsonlint-1.6.3.tgz", diff --git a/package.json b/package.json index 1ea8ab8..01b23d8 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "armview", "displayName": "ARM Template Viewer", "description": "Graphically display ARM templates in an interactive map view", - "version": "0.2.2", + "version": "0.3.0", "icon": "assets/img/icons/main.png", "publisher": "bencoleman", "author": { @@ -90,7 +90,6 @@ }, "dependencies": { "axios": "^0.19.0", - "jsonc-parser": "^2.1.1", "jsonlint": "^1.6.3", "strip-bom": "^3.0.0", "strip-json-comments": "^3.0.1", @@ -104,7 +103,6 @@ "chai-subset": "^1.6.0", "jsonlint": "^1.6.3", "mocha": "^6.2.0", - "strip-bom": "^3.0.0", "ts-loader": "^6.1.2", "tslint": "^5.16.0", "typescript": "^3.5.1" diff --git a/src/extension.ts b/src/extension.ts index 39ebac4..a258d40 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -263,7 +263,10 @@ function getWebviewContent() { - + Layout: + + +    diff --git a/src/lib/arm-parser.ts b/src/lib/arm-parser.ts index 0c883c2..b9a1c9b 100644 --- a/src/lib/arm-parser.ts +++ b/src/lib/arm-parser.ts @@ -45,22 +45,15 @@ class ARMParser { // Try to parse JSON file try { - // Strip out BOM characters for those mac owning weirdos - templateJSON = stripBom(templateJSON); - // ARM templates do allow comments, but it's not part of the JSON spec - templateJSON = stripJsonComments(templateJSON); - - // Switched to jsonlint for more meaningful error messages - this.template = jsonLint.parse(templateJSON); + this.template = this._parseJSON(templateJSON); } catch(err) { - err.message = "This template file is not valid JSON, please correct the errors below\n\n" + err.message throw err; } // Some simple validation it is an ARM template - if(!this.template.resources - || !this.template.$schema - || !this.template.$schema.toString().toLowerCase().includes("deploymenttemplate.json")) { + if(!this.template.resources || + !this.template.$schema || + !this.template.$schema.toString().toLowerCase().includes("deploymenttemplate.json")) { throw new Error("File doesn't appear to be an ARM template, but is valid JSON"); } @@ -85,6 +78,50 @@ class ARMParser { return this.elements; } + // + // Try to parse JSON file with the various "relaxations" to the JSON spec that ARM permits + // + private _parseJSON(content: string) { + try { + // Strip out BOM characters for those mac owning weirdos + content = stripBom(content); + + // ARM templates do allow comments, but it's not part of the JSON spec + content = stripJsonComments(content); + + // ARM also allows for multi-line strings, which is AWFUL + // This is a crude attempt to cope with them by simply stripping the newlines if we find any + + // Find all strings in double quotes (thankfully JSON only allows double quotes) + let re = /(".*?")/gims; + let match; + while ((match = re.exec(content)) != null) { + let string = match[1]; + // Only work on strings that include a newline (or \n\r) + if(string.includes('\n')) { + console.log(`### ArmView: Found a multi-line string in your template at offset ${match.index}. Attempting to rectify to valid JSON`); + + // Mangle the content ripping the matched string out + let front = content.substr(0, match.index); + let back = content.substr(match.index+string.length, content.length); + // Brute force removal! + // We preserve whitespace, but not sure if it's correct. We're outside the JSON spec! + let cleanString = string.replace(/\n/g, ''); //string.replace(/\s*\n\s*/g, ' '); + cleanString = cleanString.replace(/\r/g, ''); + + // Glue it back together + content = front + cleanString + back; + } + } + + // Switched to jsonlint for more meaningful error messages + return jsonLint.parse(content); + } catch(err) { + err.message = "File is not valid JSON, please correct the error(s) below\n\n" + err.message + throw err; + } + } + // // Pre-parser function, does some work to make life easier for the main parser // @@ -131,7 +168,7 @@ class ARMParser { else res.type = res.type.toLowerCase(); - // Assign a hashed id & full qualified name + // Assign a hashed id & full qualified name res.id = utils.hashCode(this.name + '_' + res.type + '_' + res.name); res.fqn = res.type + '/' + res.name; @@ -152,21 +189,17 @@ class ARMParser { // Try to parse JSON file let paramObject; + // Try to parse JSON file try { - // Strip out BOM characters for those mac owning weirdos - parameterJSON = stripBom(parameterJSON); - // ARM parameters files do allow comments, but it's not part of the JSON spec - parameterJSON = stripJsonComments(parameterJSON); - - // Switched to jsonlint for more meaningful error messages - paramObject = jsonLint.parse(parameterJSON); + paramObject = this._parseJSON(parameterJSON); } catch(err) { - err.message = "The parameter file is not valid JSON, please correct the errors below\n\n" + err.message throw err; } // Some simple ARM parameters validation - if(!paramObject.parameters || !paramObject.$schema || !paramObject.$schema.toString().includes("deploymentParameters.json")) { + if(!paramObject.parameters || + !paramObject.$schema || + !paramObject.$schema.toString().toLowerCase().includes("deploymentparameters.json")) { throw new Error("File doesn't appear to be an ARM parameters file, but is valid JSON"); } @@ -234,8 +267,8 @@ class ARMParser { } // Handle linked templates, oh boy, this is a whole world of pain - let linkedNodeCount: number = 0; - if(res.type == 'microsoft.resources/deployments' && res.properties.templateLink) { + let linkedNodeCount: number = -1; + if(res.type == 'microsoft.resources/deployments' && res.properties && res.properties.templateLink && res.properties.templateLink.uri) { let linkUri = res.properties.templateLink.uri; linkUri = this._evalExpression(linkUri, true); @@ -316,7 +349,7 @@ class ARMParser { if(searchResult && searchResult.length > 0) { console.log(`### ArmView: Found & using file: ${searchResult[0]}`); let fileContent = await vscode.workspace.fs.readFile(searchResult[0]) - subTemplate = fileContent.toString() + subTemplate = fileContent.toString(); } } catch(err) { console.log("### ArmView: Warn! Local file error: "+err); @@ -325,7 +358,7 @@ class ARMParser { } } - // If we have some data in subTemplate we were successful somehow reading the linked template! + // If we have some data in subTemplate we were successful somehow reading the linked template! if(subTemplate) { linkedNodeCount = await this._parseLinkedOrNested(res, subTemplate) } else { @@ -334,7 +367,7 @@ class ARMParser { } // For nested templates - if(res.type == 'microsoft.resources/deployments' && res.properties.template) { + if(res.type == 'microsoft.resources/deployments' && res.properties && res.properties.template) { let subTemplate; try { console.log("### ArmView: Processing nested template in: "+res.name); @@ -412,35 +445,21 @@ class ARMParser { } } - if(linkedNodeCount == 0) { - // Stick resource node in resulting elements list - this.elements.push({ - group: "nodes", - data: { - id: res.id, - name: utils.encode(res.name), - img: img, - kind: res.kind ? res.kind : '', - type: res.type, - label: label, - location: utils.encode(res.location), - extra: extraData - } - }); - } else { - // This is a special group/container node for linked templates - // We give it same name/label as the 'deployments' resource would have - this.elements.push({ - group: "nodes", - data: { - name: utils.encode(res.name), - label: label, - id: res.id, - img: img, - type: res.type, - } - }) + // Stick resource node in resulting elements list + let cytoNode = { + group: "nodes", + data: { + id: res.id, + name: utils.encode(res.name), + img: img, + kind: res.kind ? res.kind : '', + type: res.type, + label: label, + location: res.location ? utils.encode(res.location) : '', + extra: extraData + } } + this.elements.push(cytoNode); // Serious business - find the dependencies between resources if(res.dependsOn) { @@ -480,7 +499,7 @@ class ARMParser { }) } - private async _parseLinkedOrNested(res: any, subTemplate: string): Promise { + private async _parseLinkedOrNested(res: any, subTemplate: string): Promise { // If we've got some actual data, means we read the linked file somehow if(subTemplate) { let subParser = new ARMParser(this.extensionPath, res.name, this.reporter, this.editor); @@ -493,8 +512,11 @@ class ARMParser { } for(let subres of linkRes) { - // !IMPORTANT! Setting parent puts these sub-resources into a group, which will have been created - subres.data.parent = res.id; + // !IMPORTANT! Only set the parent if it's not already set + // Otherwise we overwrite the value when working with multiple levels deep of linkage + if(!subres.data.parent) + subres.data.parent = res.id; + // Push linked resources into the main list this.elements.push(subres); } @@ -593,7 +615,10 @@ class ARMParser { resid = resid.replace(/^\//, ''); resid = resid.replace(/\/\//, '/'); return resid; - } + } + if(funcName == 'copyIndex') { + return 0 + } } // It looks like a string literal @@ -635,7 +660,7 @@ class ARMParser { && !(propAccessor.charAt(1) >= '0' && propAccessor.charAt(1) <= '9') && !(propAccessor.charAt(1) == "'")) { // Evaluate propAccessor in case it includes an expression - let propAccessorResolved = this._evalExpression(propAccessor) + let propAccessorResolved = this._evalExpression(propAccessor, false) // If we get a string back it need's quoting, e.g. foo['baz'] if(typeof(propAccessorResolved) == 'string') { propAccessorResolved = `'${propAccessorResolved}'` diff --git a/test.js b/test.js deleted file mode 100644 index b6ab6cc..0000000 --- a/test.js +++ /dev/null @@ -1,51 +0,0 @@ -const jsc = require('jsonc-parser'); -const fs = require('fs'); - -let template = fs.readFileSync('./test/ref/jsontest.json', {encoding: 'utf8'}); - - -let regex = /(".*?")/gims -let matches = template.match(regex) -if(matches) { - for(match of matches) { - let bad = match.includes('\n') - if(bad) console.log("M="+match); - } -} - - - - - -let scanner = jsc.createScanner(template, true); - -// var kind; -// while ((kind = scanner.scan()) !== 17) { -// if(scanner.getTokenError()) { -// console.log(scanner.getTokenOffset(), scanner.getTokenError(), scanner.getTokenValue()); -// } -// } - -let err =[] -let res = jsc.parse(template, err) - -//console.log(template); - -for(let i = 0; i < err.length; i++) { - let e = err[i]; - if(e.error == 12) { - //console.log(e.error, e.length, template.charAt(e.offset), template.substr(e.offset, e.length)) - - // let start = e.offset - // let ni = i+1; - // for(ni; ni < err.length; ni++) { - // let ne = err[ni]; - // if(ne.error == 12) { - // console.log("ERRR! "+ni); - // i = ni; - // break; - // } - // } - } - //console.log(e.error, e.length, template.charAt(e.offset), template.substr(e.offset, e.length)) -} diff --git a/test/ref/jsontest.json b/test/ref/jsontest.json deleted file mode 100644 index eb96272..0000000 --- a/test/ref/jsontest.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "ghost snake - lemon - hat stuff", - - /* "parameters": {}, */ - - "variables": { - "foo": "this is a long - - - - - string of words" - }, - - // This is a comment - "resources": [ - - ] -} \ No newline at end of file diff --git a/test/ref/linked.json b/test/ref/linked.json new file mode 100644 index 0000000..566ca5e --- /dev/null +++ b/test/ref/linked.json @@ -0,0 +1,70 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "simpleParam": { + "defaultValue": "A simple name", + "type": "string" + } + }, + "variables": { + + }, + "resources": [ + { + "name": "directLink", + "type": "microsoft.resources/deployments", + "properties": { + "templateLink": { + "uri": "https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/101-aks/azuredeploy.json" + } + } + }, + { + "name": "local1", + "type": "microsoft.resources/deployments", + "properties": { + "templateLink": { + "uri": "foo/blah/basic.json" + } + } + }, + { + "name": "local2", + "type": "microsoft.resources/deployments", + "properties": { + "templateLink": { + "uri": "foo/blah/web.json?rubbish=1" + } + }, + "dependsOn": [ "web2" ] + }, + { + "name": "web", + "type": "microsoft.resources/deployments", + "properties": { + "templateLink": { + "uri": "http://example.net/web.json" + } + } + }, + { + "name": "web2", + "type": "microsoft.resources/deployments", + "properties": { + "templateLink": { + "uri": "http://example.net/nested2/linked.json" + } + } + }, + { + "name": "two-deep", + "type": "microsoft.resources/deployments", + "properties": { + "templateLink": { + "uri": "http://example.net/nested2/two-deep.json" + } + } + } + ] +} \ No newline at end of file diff --git a/test/ref/nested1/web.json b/test/ref/nested1/web.json new file mode 100644 index 0000000..d92bdbe --- /dev/null +++ b/test/ref/nested1/web.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + + "resources": [ + { + "name": "nestedWebSite1", + "type": "microsoft.web/sites/sourceControls" + } + ] +} \ No newline at end of file diff --git a/test/ref/nested2/linked.json b/test/ref/nested2/linked.json new file mode 100644 index 0000000..77ca52e --- /dev/null +++ b/test/ref/nested2/linked.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + + "resources": [ + { + "name": "nestedWebSite2", + "type": "microsoft.web/sites/slots" + } + ] +} \ No newline at end of file diff --git a/test/ref/nested2/two-deep.json b/test/ref/nested2/two-deep.json new file mode 100644 index 0000000..7205fa8 --- /dev/null +++ b/test/ref/nested2/two-deep.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + + "resources": [ + { + "name": "function", + "type": "microsoft.web/functionapp" + }, + { + "name": "appGw", + "type": "microsoft.resources/deployments", + "properties": { + "templateLink": { + "uri": "https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/solace-message-router/azuredeploy.json" + } + } + } + ] +} \ No newline at end of file diff --git a/test/ref/t.json b/test/ref/t.json deleted file mode 100644 index f09a179..0000000 --- a/test/ref/t.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "resources": [ - { - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2019-04-01", - "name": "bc53535353", - "location": "[resourceGroup().location]", - "kind": "StorageV2", - "sku": { - "name": "Standard_LRS" - }, - "tags": { - "cheese": "goat - hat - cat lemon - blah 123" - } - } - ] - -} \ No newline at end of file diff --git a/test/ref/t.parameters.json b/test/ref/t.parameters.json deleted file mode 100644 index 9c313f0..0000000 --- a/test/ref/t.parameters.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - } -} \ No newline at end of file diff --git a/test/ref/vars-params.json b/test/ref/vars-params.json index 9dc6e19..c71bacd 100644 --- a/test/ref/vars-params.json +++ b/test/ref/vars-params.json @@ -25,7 +25,8 @@ "Iommi", "Butler", "Ward" - ] + ], + "thisIsOne": 1 }, "resources": [ { @@ -52,11 +53,21 @@ // Name should be: "Waters" "name": "[variables('objVar')['someProp'].nameList[2]]", "type": "microsoft.compute/disks" + }, + { + // Name should be: "Bowie" + "name": "[variables('objVar').someProp.nameList[copyIndex()]]", + "type": "microsoft.compute/disks" }, + { + // Name should be: "Osbourne" + "name": "[variables('arrayVar')[0]]", + "type": "microsoft.compute/disks" + }, { // Name should be: "Iommi" - "name": "[variables('arrayVar')[1]]", + "name": "[variables('arrayVar')[variables('thisIsOne')]]", "type": "microsoft.compute/disks" - } + } ] } \ No newline at end of file diff --git a/test/reference.test.js b/test/reference.test.js index c1bb14b..4d28144 100644 --- a/test/reference.test.js +++ b/test/reference.test.js @@ -20,6 +20,9 @@ async function loadTemplate(filename) { } } +// +// +// describe('Test: basic.json', function() { let res; it('Parse file', async function() { @@ -39,19 +42,41 @@ describe('Test: basic.json', function() { }) }); +// +// +// describe('Test: vars-params.json', function() { let res; it('Parse file', async function() { res = await loadTemplate("test/ref/vars-params.json"); }); it('Validate nodes & edges', async function() { - expect(res).to.have.lengthOf(6); + expect(res).to.have.lengthOf(8); expect(res).to.be.an("array").to.containSubset([{data:{name:"Lou%20Reed"}}]); expect(res).to.be.an("array").to.containSubset([{data:{name:"Waters"}}]); expect(res).to.be.an("array").to.containSubset([{data:{name:"Zappa"}}]); + expect(res).to.be.an("array").to.containSubset([{data:{name:"Bowie"}}]); expect(res).to.be.an("array").to.containSubset([{data:{name:"Iommi"}}]); + expect(res).to.be.an("array").to.containSubset([{data:{name:"Osbourne"}}]); expect(res).to.be.an("array").to.containSubset([{data:{name:"Cheese_A%20simple%20var"}}]); expect(res).to.be.an("array").to.containSubset([{data:{name:"A%20simple%20var_A%20simple%20name"}}]); }) }); + +// +// +// +describe('Test: linked.json', function() { + let res; + it('Parse file', async function() { + res = await loadTemplate("test/ref/linked.json"); + }); + + it('Validate nodes & edges', async function() { + expect(res).to.have.lengthOf(7); + expect(res).to.be.an("array").to.containSubset([{data:{name:"aks101cluster"}}]); + + // !NOTE! Without a VS Code instance/workspace we can't fully test linked template resolution + }) +});