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
85 changes: 85 additions & 0 deletions packages/aws-cdk-lib/core/lib/private/metadata-resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,43 @@ export function formatAnalytics(infos: ConstructInfo[], enableAdditionalTelemtry
return analyticsString;
}

/**
* Takes an analytics string and converts it back into a readable format.
* Useful for debugging.
*
* @internal
*/
export function parseAnalytics(analyticsString: string): ConstructInfo[] {
const analyticsData = analyticsString.split(':');
if (analyticsData.length >= 3 && analyticsData[0] === 'v2' && analyticsData[1] === 'deflate64') {
const buffer = Buffer.from(analyticsData[2], 'base64');
const decompressedBuffer = zlib.gunzipSync(buffer);
const prefixEncodedList = decompressedBuffer.toString('utf8');
const trie = parsePrefixEncodedList(prefixEncodedList);
return trieToConstructInfos(trie);
} else {
throw new AssumptionError(`Invalid analytics string: ${analyticsString}`);
}
}

/**
* Converts a Trie back to a list of ConstructInfo objects.
*/
function trieToConstructInfos(trie: Trie): ConstructInfo[] {
const infos: ConstructInfo[] = [];
function traverse(node: Trie, path: string) {
if (node.size === 0) {
const [version, fqn] = path.split('!');
infos.push({ version, fqn });
}
for (const [key, value] of node.entries()) {
traverse(value, path + key);
}
}
traverse(trie, '');
return infos;
}

/**
* Splits after non-alphanumeric characters (e.g., '.', '/') in the FQN
* and insert each piece of the FQN in nested map (i.e., simple trie).
Expand Down Expand Up @@ -163,6 +200,54 @@ function prefixEncodeTrie(trie: Trie) {
return prefixEncoded;
}

/**
* Parses a prefix-encoded "trie-ish" structure.
* This is the inverse of `prefixEncodeTrie`.
*
* Example input:
* A{B{C,D},EF}
*
* Becomes:
* ABC,ABD,AEF
*
* Example trie:
* A --> B --> C
* | \--> D
* \--> E --> F
*/
function parsePrefixEncodedList(data: string): Trie {
const trie = new Trie();
let i = 0;

function parse(currentTrie: Trie, prefix: string) {
let token = '';
while (i < data.length) {
const char = data[i];
if (char === '{') {
i++;
parse(currentTrie, prefix + token);
token = '';
} else if (char === '}' || char === ',') {
if (token) {
insertFqnInTrie(prefix + token, trie);
}
i++;
if (char === '}') return;
token = '';
} else {
token += char;
i++;
}
}
if (token) {
insertFqnInTrie(prefix + token, trie);
}
}

parse(trie, '');
return trie;
}

/**
* Sets the OS flag to "unknown" in order to ensure we get consistent results across operating systems.
*
Expand Down
39 changes: 38 additions & 1 deletion packages/aws-cdk-lib/core/test/metadata-resource.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as zlib from 'zlib';
import { Construct } from 'constructs';
import { ENABLE_ADDITIONAL_METADATA_COLLECTION } from '../../cx-api';
import { App, Stack, IPolicyValidationPluginBeta1, IPolicyValidationContextBeta1, Stage, PolicyValidationPluginReportBeta1, FeatureFlags, Duration } from '../lib';
import { formatAnalytics } from '../lib/private/metadata-resource';
import { formatAnalytics, parseAnalytics } from '../lib/private/metadata-resource';
import { ConstructInfo } from '../lib/private/runtime-info';

describe('MetadataResource', () => {
Expand Down Expand Up @@ -222,6 +222,43 @@ describe('formatAnalytics', () => {
}
});

describe('parseAnalytics', () => {
test('parseAnalytics is the inverse of formatAnalytics for single construct', () => {
const constructInfo = [{ fqn: 'aws-cdk-lib.Construct', version: '1.2.3' }];
const analytics = formatAnalytics(constructInfo);
expect(parseAnalytics(analytics)).toEqual(constructInfo);
});

test('parseAnalytics is the inverse of formatAnalytics for multiple constructs', () => {
const constructInfo = [
{ fqn: 'aws-cdk-lib.Construct', version: '1.2.3' },
{ fqn: 'aws-cdk-lib.CfnResource', version: '1.2.3' },
{ fqn: 'aws-cdk-lib.Stack', version: '1.2.3' },
];
const analytics = formatAnalytics(constructInfo);
expect(parseAnalytics(analytics)).toEqual(constructInfo);
});

test('parseAnalytics is the inverse of formatAnalytics for nested modules', () => {
const constructInfo = [
{ fqn: 'aws-cdk-lib.Construct', version: '1.2.3' },
{ fqn: 'aws-cdk-lib.aws_servicefoo.CoolResource', version: '1.2.3' },
{ fqn: 'aws-cdk-lib.aws_servicefoo.OtherResource', version: '1.2.3' },
];
const analytics = formatAnalytics(constructInfo);
expect(parseAnalytics(analytics)).toEqual(constructInfo);
});

test('parseAnalytics is the inverse of formatAnalytics for different versions', () => {
const constructInfo = [
{ fqn: 'aws-cdk-lib.Construct', version: '1.2.3' },
{ fqn: 'aws-cdk-lib.CoolResource', version: '0.1.2' },
];
const analytics = formatAnalytics(constructInfo);
expect(parseAnalytics(analytics)).toEqual(constructInfo);
});
});

function plaintextConstructsFromAnalytics(analytics: string) {
return zlib.gunzipSync(Buffer.from(analytics.split(':')[2], 'base64')).toString('utf-8');
}
Expand Down
Loading