az-deployment-denoise
Version:
Azure deployment what-if denoiser
304 lines • 12.1 kB
JavaScript
;
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