Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 77 additions & 38 deletions packages/@aws-cdk/service-spec-importers/src/diff-fmt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
UpdatedResource,
UpdatedService,
UpdatedTypeDefinition,
VendedLog,
VendedLogs,
Event,
EventTypeDefinition,
EventProperty,
Expand Down Expand Up @@ -334,57 +334,96 @@ export class DiffFormatter {
});
}

private renderVendedLogsDiff(diff: ScalarDiff<VendedLog | undefined> | undefined): PrintableTree[] {
if (!diff || (!diff.old && !diff.new)) {
private renderVendedLogsDiff(diff: ScalarDiff<VendedLogs[] | undefined> | undefined): PrintableTree[] {
if (!diff) {
return [];
}
const tree: PrintableTree[] = [];

if (!diff.old && diff.new) {
return [this.renderVendedLogsType(diff.new).prefix([chalk.green(ADDITION), ' '], [' '])];
// Handle case where vendedLogs is added
if (!diff.old && diff.new && Array.isArray(diff.new)) {
return diff.new.map((vl) => this.renderVendedLog(vl).prefix([chalk.green(ADDITION), ' '], [' ']));
}
if (diff.old && !diff.new) {
return [this.renderVendedLogsType(diff.old).prefix([chalk.red(REMOVAL), ' '], [' '])];

// Handle case where vendedLogs is removed
if (diff.old && Array.isArray(diff.old) && !diff.new) {
return diff.old.map((vl) => this.renderVendedLog(vl).prefix([chalk.red(REMOVAL), ' '], [' ']));
}

if (diff.old && diff.new) {
if (diff.old.permissionsVersion !== diff.new.permissionsVersion) {
tree.push(
new PrintableTree(`permissionsVersion:`).addBullets([
new PrintableTree(`- ${diff.old.permissionsVersion}`).colorize(chalk.red),
new PrintableTree(`+ ${diff.new.permissionsVersion}`).colorize(chalk.green),
]),
);
// Handle case where both old and new exist and are arrays
if (Array.isArray(diff.old) && Array.isArray(diff.new)) {
const tree: PrintableTree[] = [];
const oldMap = new Map(diff.old.map((vl) => [vl.logType, vl]));
const newMap = new Map(diff.new.map((vl) => [vl.logType, vl]));

const allLogTypes = new Set([...oldMap.keys(), ...newMap.keys()]);

for (const logType of allLogTypes) {
const oldVl = oldMap.get(logType);
const newVl = newMap.get(logType);

if (!oldVl && newVl) {
tree.push(this.renderVendedLog(newVl).prefix([chalk.green(ADDITION), ' '], [' ']));
} else if (oldVl && !newVl) {
tree.push(this.renderVendedLog(oldVl).prefix([chalk.red(REMOVAL), ' '], [' ']));
} else if (oldVl && newVl) {
const changes = this.compareVendedLogs(oldVl, newVl);
if (changes.length > 0) {
tree.push(
new PrintableTree(`logType: ${logType}`).addBullets(changes).prefix([chalk.yellow(UPDATE), ' '], [' ']),
);
}
}
}

this.renderVendedLogListDiff(diff, tree, 'logTypes');
this.renderVendedLogListDiff(diff, tree, 'destinations');
return tree;
}
return tree;

return [];
}

private renderVendedLogsType(vendedLogs: VendedLog): PrintableTree {
return new PrintableTree(`vendedLogs`).addBullets([
new PrintableTree(`permissionsVersion: ${vendedLogs.permissionsVersion}`),
new PrintableTree(`logTypes: [${vendedLogs.logTypes.join(', ')}]`),
new PrintableTree(`destinations: [${vendedLogs.destinations.join(', ')}]`),
private renderVendedLog(vl: VendedLogs): PrintableTree {
const destinations = vl.destinations
.map((d) => (d.outputFormat ? `${d.destinationType}(${d.outputFormat})` : d.destinationType))
.join(', ');
return new PrintableTree(`logType: ${vl.logType}`).addBullets([
new PrintableTree(`permissionsVersion: ${vl.permissionsVersion}`),
new PrintableTree(`destinations: [${destinations}]`),
]);
}

private renderVendedLogListDiff(diff: ScalarDiff<VendedLog | undefined>, tree: PrintableTree[], diffShown: string) {
if (diff.old && diff.new) {
// the array should correspond to the value of diffShown
const oldList = new Set(diffShown === 'logTypes' ? diff.new.logTypes : diff.new.destinations);
const newList = new Set(diffShown === 'logTypes' ? diff.new.logTypes : diff.new.destinations);
const added = [...newList].filter((t) => !oldList.has(t));
const removed = [...oldList].filter((t) => !newList.has(t));
if (added.length > 0 || removed.length > 0) {
const bullets: PrintableTree[] = [];
removed.forEach((obj) => bullets.push(new PrintableTree(`- ${obj}`).colorize(chalk.red)));
added.forEach((obj) => bullets.push(new PrintableTree(`+ ${obj}`).colorize(chalk.green)));
tree.push(new PrintableTree(`${diffShown}:`).addBullets(bullets));
}
private compareVendedLogs(old: VendedLogs, updated: VendedLogs): PrintableTree[] {
const changes: PrintableTree[] = [];

if (old.permissionsVersion !== updated.permissionsVersion) {
changes.push(
new PrintableTree(`permissionsVersion:`).addBullets([
new PrintableTree(`- ${old.permissionsVersion}`).colorize(chalk.red),
new PrintableTree(`+ ${updated.permissionsVersion}`).colorize(chalk.green),
]),
);
}

const oldDests = new Map(old.destinations.map((d) => [`${d.destinationType}:${d.outputFormat ?? ''}`, d]));
const newDests = new Map(updated.destinations.map((d) => [`${d.destinationType}:${d.outputFormat ?? ''}`, d]));

const added = [...newDests.keys()].filter((k) => !oldDests.has(k));
const removed = [...oldDests.keys()].filter((k) => !newDests.has(k));

if (added.length > 0 || removed.length > 0) {
const bullets: PrintableTree[] = [];
removed.forEach((k) => {
const d = oldDests.get(k)!;
const str = d.outputFormat ? `${d.destinationType}(${d.outputFormat})` : d.destinationType;
bullets.push(new PrintableTree(`- ${str}`).colorize(chalk.red));
});
added.forEach((k) => {
const d = newDests.get(k)!;
const str = d.outputFormat ? `${d.destinationType}(${d.outputFormat})` : d.destinationType;
bullets.push(new PrintableTree(`+ ${str}`).colorize(chalk.green));
});
changes.push(new PrintableTree(`destinations:`).addBullets(bullets));
}

return changes;
}

private renderEvent(e: Event, db: number): PrintableTree {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export function importLogSources(
{
LogType: string;
ResourceTypes: string[];
Destinations: Array<{ DestinationType: string; PermissionsVersion: string }>;
Destinations: Array<{ DestinationType: string; PermissionsVersion: string; OutputFormat: string | null }>;
}
>,
report: ProblemReport,
Expand All @@ -29,20 +29,20 @@ export function importLogSources(
);
}

const destinations = value.Destinations.map((dest) => dest.DestinationType as DestinationService);
const destinations: DestinationService[] = value.Destinations.map((dest) => ({
destinationType: dest.DestinationType,
outputFormat: dest.OutputFormat ? dest.OutputFormat : undefined,
}));

resource.vendedLogs ??= {
// we take whatever the newest permissions value is and assume that all logs in a resource use the same permissions
const newLog = {
// we take whatever the newest permissions value is and assume that all destinations for a certain logType use the same permissions
permissionsVersion: permissionValue,
logTypes: [],
destinations: [],
logType: value.LogType,
destinations: destinations,
};

resource.vendedLogs.logTypes.push(value.LogType);
// dedupes incoming destinations
const newDestinations = destinations.filter((dest) => !resource.vendedLogs!.destinations.includes(dest));

resource.vendedLogs.destinations.push(...newDestinations);
resource.vendedLogs ??= [];
resource.vendedLogs.push(newLog);
} catch (err) {
// assumes the only error we are likely to see is something relating to resource type not existing in the CFN DB
report.reportFailure(
Expand Down
94 changes: 74 additions & 20 deletions packages/@aws-cdk/service-spec-importers/test/log-sources.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ test('adds log type to resource', () => {
{
DestinationType: 'S3',
PermissionsVersion: 'V2',
OutputFormat: 'json',
},
],
},
Expand All @@ -48,6 +49,7 @@ test('adds log type to resource', () => {
{
DestinationType: 'XRAY',
PermissionsVersion: 'V2',
OutputFormat: null,
},
],
},
Expand All @@ -56,11 +58,28 @@ test('adds log type to resource', () => {
);

const res = db.lookup('resource', 'cloudFormationType', 'equals', 'AWS::Some::Type')[0];
expect(res.vendedLogs).toEqual({
permissionsVersion: 'V2',
logTypes: ['SOME_LOGS', 'TRACES'],
destinations: ['S3', 'XRAY'],
});
expect(res.vendedLogs).toEqual([
{
permissionsVersion: 'V2',
logType: 'SOME_LOGS',
destinations: [
{
destinationType: 'S3',
outputFormat: 'json',
},
],
},
{
permissionsVersion: 'V2',
logType: 'TRACES',
destinations: [
{
destinationType: 'XRAY',
outputFormat: undefined,
},
],
},
]);
});

test('adds multiple log types to resource and does not add duplicate destinations', () => {
Expand All @@ -74,6 +93,7 @@ test('adds multiple log types to resource and does not add duplicate destination
{
DestinationType: 'S3',
PermissionsVersion: 'V2',
OutputFormat: 'json',
},
],
},
Expand All @@ -84,6 +104,7 @@ test('adds multiple log types to resource and does not add duplicate destination
{
DestinationType: 'S3',
PermissionsVersion: 'V2',
OutputFormat: 'json',
},
],
},
Expand All @@ -92,11 +113,28 @@ test('adds multiple log types to resource and does not add duplicate destination
);

const res = db.lookup('resource', 'cloudFormationType', 'equals', 'AWS::Some::Type')[0];
expect(res.vendedLogs).toEqual({
permissionsVersion: 'V2',
logTypes: ['APPLICATION_LOGS', 'EVENT_LOGS'],
destinations: ['S3'],
});
expect(res.vendedLogs).toEqual([
{
permissionsVersion: 'V2',
logType: 'APPLICATION_LOGS',
destinations: [
{
destinationType: 'S3',
outputFormat: 'json',
},
],
},
{
permissionsVersion: 'V2',
logType: 'EVENT_LOGS',
destinations: [
{
destinationType: 'S3',
outputFormat: 'json',
},
],
},
]);
});

test('adds log types to multiple resources', () => {
Expand All @@ -110,6 +148,7 @@ test('adds log types to multiple resources', () => {
{
DestinationType: 'S3',
PermissionsVersion: 'V2',
OutputFormat: 'json',
},
],
},
Expand All @@ -118,18 +157,32 @@ test('adds log types to multiple resources', () => {
);

const someRes = db.lookup('resource', 'cloudFormationType', 'equals', 'AWS::Some::Type')[0];
expect(someRes.vendedLogs).toEqual({
permissionsVersion: 'V2',
logTypes: ['APPLICATION_LOGS'],
destinations: ['S3'],
});
expect(someRes.vendedLogs).toEqual([
{
permissionsVersion: 'V2',
logType: 'APPLICATION_LOGS',
destinations: [
{
destinationType: 'S3',
outputFormat: 'json',
},
],
},
]);

const otherRes = db.lookup('resource', 'cloudFormationType', 'equals', 'AWS::Other::Type')[0];
expect(otherRes.vendedLogs).toEqual({
permissionsVersion: 'V2',
logTypes: ['APPLICATION_LOGS'],
destinations: ['S3'],
});
expect(otherRes.vendedLogs).toEqual([
{
permissionsVersion: 'V2',
logType: 'APPLICATION_LOGS',
destinations: [
{
destinationType: 'S3',
outputFormat: 'json',
},
],
},
]);
});

test('does not assign logTypes if resource does not exist in Cloudformation', () => {
Expand All @@ -143,6 +196,7 @@ test('does not assign logTypes if resource does not exist in Cloudformation', ()
{
DestinationType: 'S3',
PermissionsVersion: 'V2',
OutputFormat: 'json',
},
],
},
Expand Down
22 changes: 17 additions & 5 deletions packages/@aws-cdk/service-spec-types/src/types/resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export interface Resource extends Entity {
readonly validations?: unknown;
arnTemplate?: string;
isStateful?: boolean;
vendedLogs?: VendedLog;
vendedLogs?: VendedLogs[];

/**
* Information about the taggability of this resource
Expand Down Expand Up @@ -421,20 +421,32 @@ export interface RelationshipRef {
readonly propertyName: string;
}

export type DestinationService = 'S3' | 'CWL' | 'FH' | 'XRAY';
/**
* Represents a destination a Cloudformation Resource can send logs to
*/
export interface DestinationService {
/**
* Type of destination, can be S3 | FH | CWL | XRAY
*/
readonly destinationType: string;
/**
* Format of the logs that are send to this destination, can be json | plain | w3c | raw | parquet
*/
readonly outputFormat?: string;
}

/**
* Represents the types of logs a Cloudformation Resource can produce and what destinations can consume them
*/
export interface VendedLog {
export interface VendedLogs {
/**
* What version of permissions the destination supports V1 | V2
*/
readonly permissionsVersion: string;
/**
* List of the types of logs a Cloudformation resource can produce
* Type of log a Cloudformation resource can produce
*/
readonly logTypes: string[];
readonly logType: string;
/**
* List of the destinations the can consume those logs
*/
Expand Down
4 changes: 2 additions & 2 deletions sources/LogSources/log-source-resource.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading