From 16e716668ac709e362b75ab937df2dc32fa5885f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Mrzyg=C5=82=C3=B3d?= Date: Thu, 3 Aug 2017 11:07:52 +0200 Subject: [PATCH 1/5] resource-id-handling: ResourceId is handled and concat is fixed --- app/parsers/templateParser.js | 61 ++++++++++++++++++----------- test/parsers/templateParser.spec.js | 31 +++++++++++++++ 2 files changed, 70 insertions(+), 22 deletions(-) diff --git a/app/parsers/templateParser.js b/app/parsers/templateParser.js index 6179f1d..8f16bd2 100644 --- a/app/parsers/templateParser.js +++ b/app/parsers/templateParser.js @@ -144,7 +144,7 @@ export default class TemplateParser { } } - const parametersRegex = /parameters\('[a-zA-Z0-0-9-_]{0,}'\)/g; + const parametersRegex = /parameters\(['a-zA-Z0-0-9\-_]{0,}\)/g; const parametersMatches = parametersRegex.exec(normalizedName); if (parametersMatches !== null) { @@ -162,6 +162,18 @@ export default class TemplateParser { } } + // TODO: Instead of replacing 'resourceId' to 'concat' it should + // extract logic responsible for concatening and call it here by + // adding '/' to the end of the string + const resourceIdRegex = /resourceId\(['a-zA-Z0-9-._, ()\/[\]]{0,}\)/g; + const resourceIdMatches = resourceIdRegex.exec(normalizedName); + + if (resourceIdMatches !== null) { + for (let index = 0; index < resourceIdMatches.length; index += 1) { + normalizedName = normalizedName.replace('resourceId', 'concat'); + } + } + const replaceRegex = /replace\([a-zA-Z0-9\-, ']{0,}\)/g; const replaceMatches = replaceRegex.exec(normalizedName); @@ -179,28 +191,33 @@ export default class TemplateParser { } } - const concatRegex = /concat\([a-zA-Z0-9\-_,. '[\]()]{0,}\)/g; - const concatMatches = concatRegex.exec(normalizedName); - - if (concatMatches !== null) { - for (let index = 0; index < concatMatches.length; index += 1) { - let concatMatch = concatMatches[index]; - - concatMatch = concatMatch.replace('concat(', ''); - concatMatch = concatMatch.replace(')', ''); - concatMatch = concatMatch.replace(/'/g, ''); - - const concatArgs = concatMatch.split(','); - const evalConcat = (values) => { - let concated = ''; - for (let concatIndex = 0; concatIndex < values.length; concatIndex += 1) { - concated = concated.concat(values[concatIndex]); + const matches = normalizedName.match(/concat/g); + if (matches !== null) { + for (var matchIndex = 0; matchIndex < matches.length; matchIndex += 1) { + const concatRegex = /concat\([a-zA-Z0-9\-_,.\/ '[\]()]{0,}\)/g; + const concatMatches = concatRegex.exec(normalizedName); + + if (concatMatches !== null) { + for (let index = 0; index < concatMatches.length; index += 1) { + let concatMatch = concatMatches[index]; + + concatMatch = concatMatch.replace('concat(', ''); + concatMatch = concatMatch.replace(')', ''); + concatMatch = concatMatch.replace(/'/g, ''); + + const concatArgs = concatMatch.split(','); + const evalConcat = (values) => { + let concated = ''; + for (let concatIndex = 0; concatIndex < values.length; concatIndex += 1) { + concated = concated.concat(values[concatIndex]); + } + + return concated; + }; + const concatedValue = evalConcat(concatArgs); + normalizedName = normalizedName.replace(concatMatches[index], concatedValue); } - - return concated; - }; - const concatedValue = evalConcat(concatArgs); - normalizedName = normalizedName.replace(concatMatches[index], concatedValue); + } } } diff --git a/test/parsers/templateParser.spec.js b/test/parsers/templateParser.spec.js index abad375..209b6b8 100644 --- a/test/parsers/templateParser.spec.js +++ b/test/parsers/templateParser.spec.js @@ -55,5 +55,36 @@ describe('parsers', () => { expect(result.resources[0].dependsOn[1].name).toBe('nic-2'); expect(result.resources[0].dependsOn[2].name).toBe('diagssome_unique_string'); }); + + it('should handle resourceId in resource name', () => { + const json = { + $schema: 'some_schema', + contentVersion: '1.0.0.0', + resources: [ + { + name: '[variables(\'virtualMachineName\')]', + dependsOn: [ + '[resourceId(\'Microsoft.Web/sites\', variables(\'webappName\'))]' + ] + } + ], + parameters: { + "ifttt-prefix": { + type: "string" + } + }, + variables: { + webAppName: '[concat(parameters(\'ifttt-prefix\'), \'-webapp-api\')]' + } + }; + const tp = new TemplateParser(json); + + const result = tp.parseTemplate(); + TemplateParser.normalizeNames(result); + + expect(result.schema).toBe('some_schema'); + expect(result.contentVersion).toBe('1.0.0.0'); + expect(result.resources[0].dependsOn[0].name).toBe('Microsoft.Web/sitesifttt-prefix-webapp-api'); + }); }); }); From 89730576a029f43fee4e86c8ec341f11f15e0c7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Mrzyg=C5=82=C3=B3d?= Date: Thu, 3 Aug 2017 11:11:06 +0200 Subject: [PATCH 2/5] `SyntaxError` is now caught correctly --- app/index.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/index.js b/app/index.js index babf660..ddcdfeb 100644 --- a/app/index.js +++ b/app/index.js @@ -56,6 +56,9 @@ ipcRenderer.on('open-file', (event, filename) => { } catch (e) { if (e instanceof TypeError) { store.dispatch(layoutActions.error(e.message)); + } + else if (e instanceof SyntaxError) { + store.dispatch(layoutActions.error(e.message)); } else { store.dispatch(layoutActions.error(e)); } From 75577a87da22712b5f5527512b232f6b7af2d362 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Mrzyg=C5=82=C3=B3d?= Date: Fri, 4 Aug 2017 16:50:40 +0200 Subject: [PATCH 3/5] Fixed displaying dependencies, fixed eslint errors --- app/components/Visualization.js | 2 +- app/index.js | 3 +- app/parsers/templateParser.js | 8 ++--- test/parsers/templateParser.spec.js | 45 +++++++++++++++++++++++++++-- 4 files changed, 49 insertions(+), 9 deletions(-) diff --git a/app/components/Visualization.js b/app/components/Visualization.js index 39a6105..e92e631 100644 --- a/app/components/Visualization.js +++ b/app/components/Visualization.js @@ -65,7 +65,7 @@ export default class Visualization extends Component { const dependencies = []; for (let i = 0; i < this.resources.length; i += 1) { const resource = this.resources[i]; - const id = `${resource.type}/${resource.displayName}`; + const id = `${resource.type}${resource.displayName}`; const dependsOn = resource.dependsOn || []; for (let y = 0; y < dependsOn.length; y += 1) { diff --git a/app/index.js b/app/index.js index ddcdfeb..b3b1100 100644 --- a/app/index.js +++ b/app/index.js @@ -56,8 +56,7 @@ ipcRenderer.on('open-file', (event, filename) => { } catch (e) { if (e instanceof TypeError) { store.dispatch(layoutActions.error(e.message)); - } - else if (e instanceof SyntaxError) { + } else if (e instanceof SyntaxError) { store.dispatch(layoutActions.error(e.message)); } else { store.dispatch(layoutActions.error(e)); diff --git a/app/parsers/templateParser.js b/app/parsers/templateParser.js index 8f16bd2..1c4842e 100644 --- a/app/parsers/templateParser.js +++ b/app/parsers/templateParser.js @@ -165,7 +165,7 @@ export default class TemplateParser { // TODO: Instead of replacing 'resourceId' to 'concat' it should // extract logic responsible for concatening and call it here by // adding '/' to the end of the string - const resourceIdRegex = /resourceId\(['a-zA-Z0-9-._, ()\/[\]]{0,}\)/g; + const resourceIdRegex = /resourceId\(['a-zA-Z0-9-._, ()/[\]]{0,}\)/g; const resourceIdMatches = resourceIdRegex.exec(normalizedName); if (resourceIdMatches !== null) { @@ -192,9 +192,9 @@ export default class TemplateParser { } const matches = normalizedName.match(/concat/g); - if (matches !== null) { - for (var matchIndex = 0; matchIndex < matches.length; matchIndex += 1) { - const concatRegex = /concat\([a-zA-Z0-9\-_,.\/ '[\]()]{0,}\)/g; + if (typeof matches !== 'undefined' && matches !== null) { + for (let matchIndex = 0; matchIndex < matches.length; matchIndex += 1) { + const concatRegex = /concat\([a-zA-Z0-9\-_,./ '[\]()]{0,}\)/g; const concatMatches = concatRegex.exec(normalizedName); if (concatMatches !== null) { diff --git a/test/parsers/templateParser.spec.js b/test/parsers/templateParser.spec.js index 209b6b8..815867f 100644 --- a/test/parsers/templateParser.spec.js +++ b/test/parsers/templateParser.spec.js @@ -69,8 +69,8 @@ describe('parsers', () => { } ], parameters: { - "ifttt-prefix": { - type: "string" + 'ifttt-prefix': { + type: 'string' } }, variables: { @@ -86,5 +86,46 @@ describe('parsers', () => { expect(result.contentVersion).toBe('1.0.0.0'); expect(result.resources[0].dependsOn[0].name).toBe('Microsoft.Web/sitesifttt-prefix-webapp-api'); }); + + it('should connect resource and dependency', () => { + const json = { + $schema: 'some_schema', + contentVersion: '1.0.0.0', + resources: [ + { + name: '[variables(\'functionAppName\')]', + dependsOn: [ + '[resourceId(\'Microsoft.Web/serverfarms\', parameters(\'liczniknetFunctionAppServicePlanName\'))]' + ] + }, + { + name: '[parameters(\'liczniknetFunctionAppServicePlanName\')]' + } + ], + parameters: { + liczniknetReleaseType: { + type: 'string' + }, + liczniknetFunctionAppName: { + type: 'string' + }, + liczniknetFunctionAppServicePlanName: { + type: 'string' + } + }, + variables: { + webAppName: '[concat(parameters(\'liczniknetFunctionAppName\'), \'-\', parameters(\'liczniknetReleaseType\'))]' + } + }; + const tp = new TemplateParser(json); + + const result = tp.parseTemplate(); + TemplateParser.normalizeNames(result); + + expect(result.schema).toBe('some_schema'); + expect(result.contentVersion).toBe('1.0.0.0'); + expect(result.resources[0].dependsOn[0].name).toBe('Microsoft.Web/serverfarmsliczniknetFunctionAppServicePlanName'); + expect(result.resources[1].displayName).toBe('liczniknetFunctionAppServicePlanName'); + }); }); }); From a188ca9f5f1d8810f1c41724eed21818a2544c9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Mrzyg=C5=82=C3=B3d?= Date: Mon, 7 Aug 2017 14:15:28 +0200 Subject: [PATCH 4/5] Now `TemplateParser` handles nested variables and parameters, small refactor --- app/parsers/templateParser.js | 108 +++++++++++++++++++--------- test/parsers/templateParser.spec.js | 31 ++++++++ 2 files changed, 105 insertions(+), 34 deletions(-) diff --git a/app/parsers/templateParser.js b/app/parsers/templateParser.js index 1c4842e..7ab5d06 100644 --- a/app/parsers/templateParser.js +++ b/app/parsers/templateParser.js @@ -110,7 +110,6 @@ export default class TemplateParser { for (let index = 0; index < parsedTemplate.resources.length; index += 1) { const resource = parsedTemplate.resources[index]; resource.displayName = TemplateParser.parseResourceName(resource.name, parsedTemplate); - resource.displayName = TemplateParser.parseResourceName(resource.displayName, parsedTemplate); for (let dependencyIndex = 0; dependencyIndex < resource.dependsOn.length; @@ -126,42 +125,80 @@ export default class TemplateParser { static parseResourceName(name: string, parsedTemplate: Template): string { let normalizedName = name; - const variablesRegex = /variables\([a-zA-Z-0-9-_']{0,}\)/g; - const variablesMatches = variablesRegex.exec(name); - - if (variablesMatches !== null) { - for (let index = 0; index < variablesMatches.length; index += 1) { - for (let variablesIndex = 0; - variablesIndex < parsedTemplate.variables.length; - variablesIndex += 1) { - // Remember that by default string comparison is case-sensitive - if (parsedTemplate.variables[variablesIndex].id.toUpperCase() - === variablesMatches[index].toUpperCase()) { - normalizedName = normalizedName.replace(variablesMatches[index], - parsedTemplate.variables[variablesIndex].value); + normalizedName = TemplateParser.parseVariables(normalizedName, parsedTemplate); + normalizedName = TemplateParser.parseParameters(normalizedName, parsedTemplate); + normalizedName = TemplateParser.parseResourceId(normalizedName); + normalizedName = TemplateParser.parseReplace(normalizedName); + normalizedName = TemplateParser.parseConcat(normalizedName); + + // Remove whitespaces just for the sake of normalizing names + normalizedName = normalizedName.replace(/\s+/g, ''); + + // Removed [[ and ]] which have left now + normalizedName = normalizedName.replace(/\[/g, ''); + normalizedName = normalizedName.replace(/\]/g, ''); + + return normalizedName; + } + + static parseVariables(name: string, parsedTemplate: Template): string { + let normalizedName = name; + const matches = normalizedName.match(/variables/g); + if (typeof matches !== 'undefined' && matches !== null) { + for (let matchIndex = 0; matchIndex < matches.length; matchIndex += 1) { + const variablesRegex = /variables\([a-zA-Z0-9-_']{0,}\)/g; + const variablesMatches = variablesRegex.exec(normalizedName); + + if (variablesMatches !== null) { + for (let index = 0; index < variablesMatches.length; index += 1) { + for (let variablesIndex = 0; + variablesIndex < parsedTemplate.variables.length; + variablesIndex += 1) { + // Remember that by default string comparison is case-sensitive + if (parsedTemplate.variables[variablesIndex].id.toUpperCase() + === variablesMatches[index].toUpperCase()) { + normalizedName = normalizedName.replace(variablesMatches[index], + parsedTemplate.variables[variablesIndex].value); + } + } } } } } - const parametersRegex = /parameters\(['a-zA-Z0-0-9\-_]{0,}\)/g; - const parametersMatches = parametersRegex.exec(normalizedName); - - if (parametersMatches !== null) { - for (let index = 0; index < parametersMatches.length; index += 1) { - for (let parametersIndex = 0; - parametersIndex < parsedTemplate.parameters.length; - parametersIndex += 1) { - if (parsedTemplate.parameters[parametersIndex].id === parametersMatches[index]) { - const nameToDisplay = - parsedTemplate.parameters[parametersIndex].defaultValue || - parsedTemplate.parameters[parametersIndex].name; - normalizedName = normalizedName.replace(parametersMatches[index], nameToDisplay); + return normalizedName; + } + + static parseParameters(name: string, parsedTemplate: Template): string { + let normalizedName = name; + const matches = normalizedName.match(/parameters/g); + if (typeof matches !== 'undefined' && matches !== null) { + for (let matchIndex = 0; matchIndex < matches.length; matchIndex += 1) { + const parametersRegex = /parameters\(['a-zA-Z0-0-9\-_]{0,}\)/g; + const parametersMatches = parametersRegex.exec(normalizedName); + + if (parametersMatches !== null) { + for (let index = 0; index < parametersMatches.length; index += 1) { + for (let parametersIndex = 0; + parametersIndex < parsedTemplate.parameters.length; + parametersIndex += 1) { + if (parsedTemplate.parameters[parametersIndex].id === parametersMatches[index]) { + const nameToDisplay = + parsedTemplate.parameters[parametersIndex].defaultValue || + parsedTemplate.parameters[parametersIndex].name; + normalizedName = normalizedName.replace(parametersMatches[index], nameToDisplay); + } + } } } } } + return normalizedName; + } + + static parseResourceId(name: string): string { + let normalizedName = name; // TODO: Instead of replacing 'resourceId' to 'concat' it should // extract logic responsible for concatening and call it here by // adding '/' to the end of the string @@ -174,6 +211,11 @@ export default class TemplateParser { } } + return normalizedName; + } + + static parseReplace(name: string): string { + let normalizedName = name; const replaceRegex = /replace\([a-zA-Z0-9\-, ']{0,}\)/g; const replaceMatches = replaceRegex.exec(normalizedName); @@ -191,6 +233,11 @@ export default class TemplateParser { } } + return normalizedName; + } + + static parseConcat(name: string): string { + let normalizedName = name; const matches = normalizedName.match(/concat/g); if (typeof matches !== 'undefined' && matches !== null) { for (let matchIndex = 0; matchIndex < matches.length; matchIndex += 1) { @@ -221,13 +268,6 @@ export default class TemplateParser { } } - // Remove whitespaces just for the sake or normalizing names - normalizedName = normalizedName.replace(/\s+/g, ''); - - // Removed [[ and ]] which have left now - normalizedName = normalizedName.replace(/\[/g, ''); - normalizedName = normalizedName.replace(/\]/g, ''); - return normalizedName; } } diff --git a/test/parsers/templateParser.spec.js b/test/parsers/templateParser.spec.js index 815867f..dc7cf71 100644 --- a/test/parsers/templateParser.spec.js +++ b/test/parsers/templateParser.spec.js @@ -127,5 +127,36 @@ describe('parsers', () => { expect(result.resources[0].dependsOn[0].name).toBe('Microsoft.Web/serverfarmsliczniknetFunctionAppServicePlanName'); expect(result.resources[1].displayName).toBe('liczniknetFunctionAppServicePlanName'); }); + + it('should resolve concated variables correctly in name', () => { + const json = { + $schema: 'some_schema', + contentVersion: '1.0.0.0', + resources: [ + { + name: '[concat(variables(\'ifttt-trafficmanagerName\'), \'/\', variables(\'webappName\'))]', + dependsOn: [ + ] + } + ], + parameters: { + 'ifttt-prefix': { + type: 'string' + } + }, + variables: { + 'ifttt-trafficmanagerName': '[concat(parameters(\'ifttt-prefix\'), \'-trafficmanager\')]', + webappName: '[concat(parameters(\'ifttt-prefix\'), \'-webapp-api\')]' + } + }; + const tp = new TemplateParser(json); + + const result = tp.parseTemplate(); + TemplateParser.normalizeNames(result); + + expect(result.schema).toBe('some_schema'); + expect(result.contentVersion).toBe('1.0.0.0'); + expect(result.resources[0].displayName).toBe('ifttt-prefix-trafficmanager/ifttt-prefix-webapp-api'); + }); }); }); From 3789c048ec8c1fdb27f53771fbdbe60554d4dce2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Mrzyg=C5=82=C3=B3d?= Date: Mon, 7 Aug 2017 14:30:16 +0200 Subject: [PATCH 5/5] Bump version --- app/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/package.json b/app/package.json index 5b369f2..ff73841 100644 --- a/app/package.json +++ b/app/package.json @@ -1,7 +1,7 @@ { "name": "ARMata", "productName": "ARMata", - "version": "v1.0.0-prealpha", + "version": "v1.0.0-alpha", "description": "ARM templates visualizer & editor", "main": "./main.prod.js", "author": {