packages/@aws-cdk/cloudformation-diff/lib/network/security-group-changes.ts (94 lines of code) (raw):
import * as chalk from 'chalk';
import type { RuleJson } from './security-group-rule';
import { SecurityGroupRule } from './security-group-rule';
import type { PropertyChange, ResourceChange } from '../diff/types';
import { DiffableCollection } from '../diffable';
import { renderIntrinsics } from '../render-intrinsics';
import { deepRemoveUndefined, dropIfEmpty, makeComparator } from '../util';
export interface SecurityGroupChangesProps {
ingressRulePropertyChanges: PropertyChange[];
ingressRuleResourceChanges: ResourceChange[];
egressRuleResourceChanges: ResourceChange[];
egressRulePropertyChanges: PropertyChange[];
}
/**
* Changes to IAM statements
*/
export class SecurityGroupChanges {
public readonly ingress = new DiffableCollection<SecurityGroupRule>();
public readonly egress = new DiffableCollection<SecurityGroupRule>();
constructor(props: SecurityGroupChangesProps) {
// Group rules
for (const ingressProp of props.ingressRulePropertyChanges) {
this.ingress.addOld(...this.readInlineRules(ingressProp.oldValue, ingressProp.resourceLogicalId));
this.ingress.addNew(...this.readInlineRules(ingressProp.newValue, ingressProp.resourceLogicalId));
}
for (const egressProp of props.egressRulePropertyChanges) {
this.egress.addOld(...this.readInlineRules(egressProp.oldValue, egressProp.resourceLogicalId));
this.egress.addNew(...this.readInlineRules(egressProp.newValue, egressProp.resourceLogicalId));
}
// Rule resources
for (const ingressRes of props.ingressRuleResourceChanges) {
this.ingress.addOld(...this.readRuleResource(ingressRes.oldProperties));
this.ingress.addNew(...this.readRuleResource(ingressRes.newProperties));
}
for (const egressRes of props.egressRuleResourceChanges) {
this.egress.addOld(...this.readRuleResource(egressRes.oldProperties));
this.egress.addNew(...this.readRuleResource(egressRes.newProperties));
}
this.ingress.calculateDiff();
this.egress.calculateDiff();
}
public get hasChanges() {
return this.ingress.hasChanges || this.egress.hasChanges;
}
/**
* Return a summary table of changes
*/
public summarize(): string[][] {
const ret: string[][] = [];
const header = ['', 'Group', 'Dir', 'Protocol', 'Peer'];
const inWord = 'In';
const outWord = 'Out';
// Render a single rule to the table (curried function so we can map it across rules easily--thank you JavaScript!)
const renderRule = (plusMin: string, inOut: string) => (rule: SecurityGroupRule) => [
plusMin,
rule.groupId,
inOut,
rule.describeProtocol(),
rule.describePeer(),
].map(s => plusMin === '+' ? chalk.green(s) : chalk.red(s));
// First generate all lines, sort later
ret.push(...this.ingress.additions.map(renderRule('+', inWord)));
ret.push(...this.egress.additions.map(renderRule('+', outWord)));
ret.push(...this.ingress.removals.map(renderRule('-', inWord)));
ret.push(...this.egress.removals.map(renderRule('-', outWord)));
// Sort by group name then ingress/egress (ingress first)
ret.sort(makeComparator((row: string[]) => [row[1], row[2].indexOf(inWord) > -1 ? 0 : 1]));
ret.splice(0, 0, header);
return ret;
}
public toJson(): SecurityGroupChangesJson {
return deepRemoveUndefined({
ingressRuleAdditions: dropIfEmpty(this.ingress.additions.map(s => s.toJson())),
ingressRuleRemovals: dropIfEmpty(this.ingress.removals.map(s => s.toJson())),
egressRuleAdditions: dropIfEmpty(this.egress.additions.map(s => s.toJson())),
egressRuleRemovals: dropIfEmpty(this.egress.removals.map(s => s.toJson())),
});
}
public get rulesAdded(): boolean {
return this.ingress.hasAdditions
|| this.egress.hasAdditions;
}
private readInlineRules(rules: any, logicalId: string): SecurityGroupRule[] {
if (!rules || !Array.isArray(rules)) {
return [];
}
// UnCloudFormation so the parser works in an easier domain
const ref = '${' + logicalId + '.GroupId}';
return rules.flatMap((r: any) => {
const rendered = renderIntrinsics(r);
// SecurityGroupRule is not robust against unparsed objects
return typeof rendered === 'object' ? [new SecurityGroupRule(rendered, ref)] : [];
});
}
private readRuleResource(resource: any): SecurityGroupRule[] {
if (!resource) {
return [];
}
// UnCloudFormation so the parser works in an easier domain
return [new SecurityGroupRule(renderIntrinsics(resource))];
}
}
export interface SecurityGroupChangesJson {
ingressRuleAdditions?: RuleJson[];
ingressRuleRemovals?: RuleJson[];
egressRuleAdditions?: RuleJson[];
egressRuleRemovals?: RuleJson[];
}