forked from aws/aws-cdk
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmanifest.ts
119 lines (100 loc) · 4.19 KB
/
manifest.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
import * as fs from 'fs';
import * as jsonschema from 'jsonschema';
import * as semver from 'semver';
import * as assembly from './schema';
// this prefix is used by the CLI to identify this specific error.
// in which case we want to instruct the user to upgrade his CLI.
// see exec.ts#createAssembly
export const VERSION_MISMATCH: string = 'Cloud assembly schema version mismatch';
/**
* Protocol utility class.
*/
export class Manifest {
/**
* Save manifest to file.
*
* @param manifest - manifest.
*/
public static save(manifest: assembly.AssemblyManifest, filePath: string) {
fs.writeFileSync(filePath, JSON.stringify(manifest, undefined, 2));
}
/**
* Load manifest from file.
*
* @param filePath - path to the manifest file.
*/
public static load(filePath: string): assembly.AssemblyManifest {
const raw: assembly.AssemblyManifest = JSON.parse(fs.readFileSync(filePath, 'UTF-8'));
Manifest.patchStackTags(raw);
Manifest.validate(raw);
return raw;
}
/**
* Fetch the current schema version number.
*/
public static version(): string {
// eslint-disable-next-line @typescript-eslint/no-require-imports
return require('../schema/cloud-assembly.version.json').version;
}
// eslint-disable-next-line @typescript-eslint/no-require-imports
private static schema: jsonschema.Schema = require('../schema/cloud-assembly.schema.json');
private static validate(manifest: assembly.AssemblyManifest) {
function parseVersion(version: string) {
const ver = semver.valid(version);
if (!ver) {
throw new Error(`Invalid semver string: "${version}"`);
}
return ver;
}
const maxSupported = parseVersion(Manifest.version());
const actual = parseVersion(manifest.version);
// first validate the version should be accepted.
if (semver.gt(actual, maxSupported)) {
// we use a well known error prefix so that the CLI can identify this specific error
// and print some more context to the user.
throw new Error(`${VERSION_MISMATCH}: Maximum schema version supported is ${maxSupported}, but found ${actual}`);
}
// now validate the format is good.
const validator = new jsonschema.Validator();
const result = validator.validate(manifest, Manifest.schema, {
// does exist but is not in the TypeScript definitions
nestedErrors: true,
allowUnknownAttributes: false
} as any);
if (!result.valid) {
throw new Error(`Invalid assembly manifest:\n${result}`);
}
}
/**
* This requires some explaining...
*
* We previously used `{ Key, Value }` for the object that represents a stack tag. (Notice the casing)
* @link https://github.com/aws/aws-cdk/blob/v1.27.0/packages/aws-cdk/lib/api/cxapp/stacks.ts#L427.
*
* When that object moved to this package, it had to be JSII compliant, which meant the property
* names must be `camelCased`, and not `PascalCased`. This meant it no longer matches the structure in the `manifest.json` file.
* In order to support current manifest files, we have to translate the `PascalCased` representation to the new `camelCased` one.
*
* Note that the serialization itself still writes `PascalCased` because it relates to how CloudFormation expects it.
*
* Ideally, we would start writing the `camelCased` and translate to how CloudFormation expects it when needed. But this requires nasty
* backwards-compatibility code and it just doesn't seem to be worth the effort.
*/
private static patchStackTags(manifest: assembly.AssemblyManifest) {
for (const artifact of Object.values(manifest.artifacts || [])) {
if (artifact.type === assembly.ArtifactType.AWS_CLOUDFORMATION_STACK) {
for (const metadataEntries of Object.values(artifact.metadata || [])) {
for (const metadataEntry of metadataEntries) {
if (metadataEntry.type === assembly.ArtifactMetadataEntryType.STACK_TAGS && metadataEntry.data) {
const metadataAny = metadataEntry as any;
metadataAny.data = metadataAny.data.map((t: any) => {
return { key: t.Key, value: t.Value };
});
}
}
}
}
}
}
private constructor() {}
}