Skip to content
Merged
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
1 change: 1 addition & 0 deletions packages/@aws-cdk/service-spec-importers/src/db-diff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ export class DbDiff {
name: diffScalar(a, b, 'name'),
scrutinizable: diffScalar(a, b, 'scrutinizable'),
vendedLogs: diffField(a, b, 'vendedLogs', jsonEq),
vendedLogsConfig: diffField(a, b, 'vendedLogsConfig', jsonEq),
tagInformation: diffField(a, b, 'tagInformation', jsonEq),
primaryIdentifier: collapseEmptyDiff(
diffList(a.primaryIdentifier ?? [], b.primaryIdentifier ?? [], (x, y) => x === y),
Expand Down
89 changes: 89 additions & 0 deletions packages/@aws-cdk/service-spec-importers/src/diff-fmt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
UpdatedEvent,
UpdatedEventTypeDefinition,
UpdatedEventProperty,
VendedLogs,
} from '@aws-cdk/service-spec-types';
import chalk from 'chalk';
import { PrintableTree } from './printable-tree';
Expand Down Expand Up @@ -157,6 +158,7 @@ export class DiffFormatter {
listWithCaption('properties', this.renderPropertyDiff(r.properties)),
listWithCaption('attributes', this.renderPropertyDiff(r.attributes)),
listWithCaption('vendedLogs', this.renderVendedLogsDiff(r.vendedLogs)),
listWithCaption('vendedLogsConfig', this.renderVendedLogsArrayDiff(r.vendedLogsConfig)),
listWithCaption(
'types',
this.renderMapDiff(
Expand Down Expand Up @@ -334,6 +336,93 @@ export class DiffFormatter {
});
}

private renderVendedLogsArrayDiff(diff: ScalarDiff<VendedLogs[] | undefined> | undefined): PrintableTree[] {
if (!diff) {
return [];
}

if (!diff.old && diff.new && Array.isArray(diff.new)) {
return diff.new.map((vl) => this.renderVendedLog(vl).prefix([chalk.green(ADDITION), ' '], [' ']));
}

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

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), ' '], [' ']),
);
}
}
}

return tree;
}
return [];
}

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

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]));
const newDests = new Map(updated.destinations.map((d) => [`${d.destinationType}`, 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.destinationType;
bullets.push(new PrintableTree(`- ${str}`).colorize(chalk.red));
});
added.forEach((k) => {
const d = newDests.get(k)!;
const str = d.destinationType;
bullets.push(new PrintableTree(`+ ${str}`).colorize(chalk.green));
});
changes.push(new PrintableTree(`destinations:`).addBullets(bullets));
}

return changes;
}

private renderVendedLogsDiff(diff: ScalarDiff<VendedLog | undefined> | undefined): PrintableTree[] {
if (!diff || (!diff.old && !diff.new)) {
return [];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DestinationService, SpecDatabase } from '@aws-cdk/service-spec-types';
import { DeliveryDestination, DestinationService, SpecDatabase } from '@aws-cdk/service-spec-types';
import { failure } from '@cdklabs/tskb';
import { ProblemReport, ReportAudience } from '../report';

Expand Down Expand Up @@ -29,7 +29,7 @@ export function importLogSources(
);
}

const destinations = value.Destinations.map((dest) => dest.DestinationType as DestinationService);
const oldDestinations = value.Destinations.map((dest) => dest.DestinationType as DestinationService);

resource.vendedLogs ??= {
// we take whatever the newest permissions value is and assume that all logs in a resource use the same permissions
Expand All @@ -40,9 +40,22 @@ export function importLogSources(

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

resource.vendedLogs.destinations.push(...newDestinations);

const destinations: DeliveryDestination[] = value.Destinations.map((dest) => ({
destinationType: dest.DestinationType,
}));

const newLog = {
permissionsVersion: permissionValue,
logType: value.LogType,
destinations: destinations,
};

resource.vendedLogsConfig ??= [];
resource.vendedLogsConfig.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
82 changes: 62 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 @@ -56,11 +56,26 @@ 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.vendedLogsConfig).toEqual([
{
permissionsVersion: 'V2',
logType: 'SOME_LOGS',
destinations: [
{
destinationType: 'S3',
},
],
},
{
permissionsVersion: 'V2',
logType: 'TRACES',
destinations: [
{
destinationType: 'XRAY',
},
],
},
]);
});

test('adds multiple log types to resource and does not add duplicate destinations', () => {
Expand Down Expand Up @@ -92,11 +107,26 @@ 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.vendedLogsConfig).toEqual([
{
permissionsVersion: 'V2',
logType: 'APPLICATION_LOGS',
destinations: [
{
destinationType: 'S3',
},
],
},
{
permissionsVersion: 'V2',
logType: 'EVENT_LOGS',
destinations: [
{
destinationType: 'S3',
},
],
},
]);
});

test('adds log types to multiple resources', () => {
Expand All @@ -118,18 +148,30 @@ 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.vendedLogsConfig).toEqual([
{
permissionsVersion: 'V2',
logType: 'APPLICATION_LOGS',
destinations: [
{
destinationType: 'S3',
},
],
},
]);

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

test('does not assign logTypes if resource does not exist in Cloudformation', () => {
Expand Down
1 change: 1 addition & 0 deletions packages/@aws-cdk/service-spec-types/src/types/diff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export interface UpdatedResource {
readonly tagInformation?: ScalarDiff<Resource['tagInformation']>;
readonly scrutinizable?: ScalarDiff<Resource['scrutinizable']>;
readonly vendedLogs?: ScalarDiff<Resource['vendedLogs']>;
readonly vendedLogsConfig?: ScalarDiff<Resource['vendedLogsConfig']>;
readonly typeDefinitionDiff?: MapDiff<TypeDefinition, UpdatedTypeDefinition>;
readonly primaryIdentifier?: ListDiff<string, void>;
readonly metrics?: MapDiff<Metric, ChangedMetric>;
Expand Down
29 changes: 29 additions & 0 deletions packages/@aws-cdk/service-spec-types/src/types/resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export interface Resource extends Entity {
arnTemplate?: string;
isStateful?: boolean;
vendedLogs?: VendedLog;
vendedLogsConfig?: VendedLogs[];

/**
* Information about the taggability of this resource
Expand Down Expand Up @@ -441,6 +442,34 @@ export interface VendedLog {
readonly destinations: DestinationService[];
}

/**
* Represents a delivery destination that a Cloudformation resource can send logs to
*/
export interface DeliveryDestination {
/**
* The type of service that is ingesting the logs, can be S3 | FH | CWL | XRAY
*/
readonly destinationType: string;
}

/**
* Represents a type of log that a Cloudformation Resource can produce and what destinations can consume them
*/
export interface VendedLogs {
/**
* What version of permissions the destination supports V1 | V2
*/
readonly permissionsVersion: string;
/**
* Type of log a Cloudformation resource can produce
*/
readonly logType: string;
/**
* List of the destinations the can consume those logs
*/
readonly destinations: DeliveryDestination[];
}

export class RichPropertyType {
constructor(private readonly type: PropertyType) {}

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