UNPKG

az-deployment-denoise

Version:
304 lines 12.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.test = void 0; exports.printPretty = printPretty; const azure_1 = require("./utils/azure"); const consolePrinter = { printLine: function (line = '', level = 0, changeType = null) { const indent = Array(level).fill(' ').join(''); let sign; switch (changeType) { case null: sign = ''; break; case '': sign = ' '; break; default: sign = `${signForChangeType(changeType)} `; } console.log(`${indent}${sign}${line}`); }, }; const changeTypeOrder = { Delete: 1, Create: 2, Modify: 3, 'Array': 3, NoChange: 4, NoEffect: 4, Ignore: 5, }; function signForChangeType(t) { switch (t) { case 'Create': return '+'; case 'Modify': return '~'; case 'Array': return '~'; case 'Delete': return '-'; case 'NoChange': return '='; case 'Ignore': return '*'; case 'NoEffect': return 'x'; } } function changeTypeSetOfPropertyChange(propertyChange, changeTypeSet) { changeTypeSet.add(propertyChange.propertyChangeType); if (propertyChange.children) { propertyChange.children.forEach(pc => { changeTypeSetOfPropertyChange(pc, changeTypeSet); }); } } function changeTypeSetOfOperationResult(operationResult) { const changeTypeSet = new Set(); operationResult.changes.forEach(c => { changeTypeSet.add(c.changeType); if (c.changeType == 'Modify') { c.delta.forEach(pc => { changeTypeSetOfPropertyChange(pc, changeTypeSet); }); } }); return changeTypeSet; } function printPretty(operationResult, printer = consolePrinter) { const pretty = organizeResult(operationResult); printer.printLine('Note: The result may contain false positive predictions (noise).'); printer.printLine('You can help us improve the accuracy of the result by opening an issue here: https://aka.ms/WhatIfIssues'); printer.printLine('This result has been filtered using az-deployment-denoise: https://github.com/ottijp/az-deployment-denoise'); printer.printLine(); printer.printLine('Resource and property changes are indicated with these symbols:'); const changeTypes = Array.from(changeTypeSetOfOperationResult(operationResult)) .sort((lhs, rhs) => changeTypeOrder[lhs] - changeTypeOrder[rhs]); changeTypes.forEach(ctype => { let description; switch (ctype) { case 'Create': description = 'Create'; break; case 'Delete': description = 'Delete'; break; case 'Modify': description = 'Modify'; break; case 'NoChange': description = 'Nochange'; break; case 'Ignore': description = 'Ignore'; break; case 'Array': description = 'Modify'; break; case 'NoEffect': description = 'Noeffect'; break; } printer.printLine(description, 1, ctype); }); printer.printLine(); printer.printLine('The deployment will update the following scope:'); printer.printLine(); for (const scope of pretty.scopes) { printer.printLine(`Scope: ${scope.id}`); printer.printLine(); for (const change of scope.changes) { const pathInScope = (0, azure_1.parseAzureResource)(change.resourceId).pathInScope; switch (change.changeType) { case 'Create': delete change.after['resourceGroup']; printer.printLine(`${pathInScope} [${change.after['apiVersion']}]`, 1, change.changeType); printer.printLine(); printObject(printer, 2, change.after); printer.printLine(); break; case 'Delete': printer.printLine(`${pathInScope}`, 1, change.changeType); printer.printLine(); printObject(printer, 2, change.before); printer.printLine(); break; case 'Modify': printer.printLine(`${pathInScope} [${change.after['apiVersion']}]`, 1, change.changeType); printPropertyChange(printer, 2, change.delta); printer.printLine(); break; case 'NoChange': printer.printLine(`${pathInScope} [${change.after['apiVersion']}]`, 1, change.changeType); break; case 'Ignore': printer.printLine(`${pathInScope}`, 1, change.changeType); break; } } } const allChangeTypes = pretty.scopes.map(s => s.changes).flatMap(c => c).map(c => c.changeType); const changeTypeSummary = allChangeTypes.reduce((acc, value) => { acc[value] = (acc[value] || 0) + 1; return acc; }, {}); const changeTypeSummaryOrdered = Object.entries(changeTypeSummary) .map(([value, count]) => ({ value, count })) .sort((lhs, rhs) => changeTypeOrder[lhs.value] - changeTypeOrder[rhs.value]); const changeTypesDescription = changeTypeSummaryOrdered .filter(c => c.value != 'Ignore') .map(c => { switch (c.value) { case 'Create': return `${c.count} to create`; case 'Delete': return `${c.count} to delete`; case 'Modify': return `${c.count} to modify`; case 'NoChange': return `${c.count} no change`; } }).join(', '); printer.printLine(); printer.printLine(`Resource changes: ${changeTypesDescription}.`); } function stringifyPrimitiveValue(value) { switch (typeof value) { case 'string': return `"${value}"`; case 'number': case 'boolean': return `${value}`; default: throw Error(`invalid value type: JSON.stiringify(value)`); } } function printObject(printer, level, obj, path = []) { const flattenObj = flattenObject(obj); const maxKeyLength = Object.keys(flattenObj).reduce((acc, cur) => Math.max(acc, cur.length), 0) + 2; for (const key in flattenObj) { const fullKey = path.length === 0 ? key : `${path.join('.')}.${key}`; const fullKeyPadded = `${fullKey}: `.padEnd(maxKeyLength); const value = flattenObj[key]; if (Array.isArray(value)) { printer.printLine(`${fullKey}: [`, level, ''); value.forEach((elm, idx) => { if (typeof elm === 'object') { printer.printLine(`${idx}:`, level + 1, ''); printer.printLine(); printObject(printer, level + 2, elm, []); printer.printLine(); } else { printer.printLine(`${idx}: ${JSON.stringify(elm)}`, level + 1, ''); } }); printer.printLine(']', level, ''); } else if (typeof value === 'object') { printObject(printer, level, value, [...path, key]); } else { printer.printLine(`${fullKeyPadded}${stringifyPrimitiveValue(value)}`, level, ''); } } } function printPropertyChange(printer, level, deltas) { const maxKeyLength = deltas.map(elm => elm.path).reduce((acc, cur) => Math.max(acc, cur.length), 0) + 2; deltas.forEach(delta => { switch (delta.propertyChangeType) { case 'Create': case 'Delete': case 'NoEffect': { const valueToPrint = delta.propertyChangeType === 'Delete' ? delta.before : delta.after; if (Array.isArray(valueToPrint)) { printer.printLine(`${delta.path}: [`, level, delta.propertyChangeType); valueToPrint.forEach((elm, idx) => { if (typeof elm === 'object') { printer.printLine(`${idx}:`, level + 1, ''); printer.printLine(); printObject(printer, level + 2, elm, []); printer.printLine(); } else { printer.printLine(`${idx}: ${JSON.stringify(elm)}`, level + 1, ''); } }); printer.printLine(']', level, ''); } else if (typeof valueToPrint === 'object') { printer.printLine(`${delta.path}:`, level, delta.propertyChangeType); printer.printLine(); printObject(printer, level + 1, valueToPrint, []); printer.printLine(); } else { const fullKeyPadded = `${delta.path}: `.padEnd(maxKeyLength); printer.printLine(`${fullKeyPadded}${stringifyPrimitiveValue(valueToPrint)}`, level, delta.propertyChangeType); } break; } case 'Modify': { if (delta.children !== null) { printer.printLine(`${delta.path}:`, level, delta.propertyChangeType); printer.printLine(); printPropertyChange(printer, level + 1, delta.children); } else { const fullKeyPadded = `${delta.path}: `.padEnd(maxKeyLength); printer.printLine(`${fullKeyPadded}${JSON.stringify(delta.before)} => ${JSON.stringify(delta.after)}`, level, delta.propertyChangeType); } break; } case 'Array': { printer.printLine(`${delta.path}: [`, level, 'Array'); const sortedChildren = delta.children.sort((lhs, rhs) => parseInt(lhs.path) - parseInt(rhs.path)); printPropertyChange(printer, level + 1, sortedChildren); printer.printLine(); printer.printLine(']', level, ''); break; } } }); } function organizeResult(result) { const resourceGroupIds = new Set; result.changes.forEach(elm => resourceGroupIds.add((0, azure_1.parseAzureResource)(elm.resourceId).resourceGroup.id)); return { scopes: Array.from(resourceGroupIds).map(rgid => ({ id: rgid, changes: result.changes .filter(elm => (0, azure_1.parseAzureResource)(elm.resourceId).resourceGroup.id === rgid) .sort((lhs, rhs) => changeTypeOrder[lhs.changeType] - changeTypeOrder[rhs.changeType]) .map(elm => { if (elm.delta) { elm.delta = elm.delta.sort((lhs, rhs) => changeTypeOrder[lhs.propertyChangeType] - changeTypeOrder[rhs.propertyChangeType]); } return elm; }), })), }; } function flattenObject(obj, path = []) { let ret = {}; for (const key in obj) { const fullKey = path.length === 0 ? key : `${path.join('.')}.${key}`; const value = obj[key]; if (Array.isArray(value)) { ret[fullKey] = value.map((elm) => { if (typeof elm === 'object') { return flattenObject(elm, []); } else { return elm; } }); } else if (typeof value === 'object') { ret = Object.assign(ret, flattenObject(value, [...path, key])); } else { ret[fullKey] = value; } } return ret; } exports.test = { printPretty, printObject, printPropertyChange, flattenObject, organizeResult, }; //# sourceMappingURL=writer.js.map