@znemz/cft-utils 0.0.5-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.js ADDED
@@ -0,0 +1,5 @@
1
+ const resources = require('./resources');
2
+
3
+ module.exports = {
4
+ resources,
5
+ };
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ taggable: require('./taggable'),
3
+ };
@@ -0,0 +1,133 @@
1
+ const path = require('path');
2
+ const fs = require('fs/promises');
3
+ const globby = require('globby');
4
+
5
+ const cachePath = path.join(__dirname, '..', 'cache');
6
+
7
+ /*
8
+ * @returns {Promise<string[]>}
9
+ *
10
+ * Returns list of taggable resources from a cache file
11
+ * or from reading the aws-cdk-lib ITaggable Resources
12
+ */
13
+ const getResources = async () => {
14
+ // use cached list if it exists in ${cachePath}/taggable.json
15
+ const cacheFile = path.join(cachePath, 'cft_taggable_resources.json');
16
+ let taggableResources;
17
+ taggableResources = await getCachedFile(cacheFile);
18
+ if (taggableResources) {
19
+ return taggableResources;
20
+ }
21
+ // note: should be called during prepublishing only
22
+ // cache miss, generate list of taggable resources
23
+ taggableResources = await getResourcesFromAwsCdk();
24
+ await mkCacheDir();
25
+ await fs.writeFile(cacheFile, JSON.stringify(taggableResources, null, 2));
26
+ return taggableResources;
27
+ };
28
+
29
+ /*
30
+ * Private as this should not be called from consuming code
31
+ * @returns {Promise<string[]>}
32
+ *
33
+ * Matches comments of an aws-* inside aws-cdk-lib
34
+ * resource file "*generated.d.ts" definition
35
+ * looking for @cloudformationResource and ITaggable
36
+ */
37
+ const getResourcesFromAwsCdk = async () => {
38
+ // note: should be called during prepublishing only
39
+ const cdkDir = path.dirname(require.resolve('aws-cdk-lib'));
40
+ const globPaths = [
41
+ path.join(cdkDir, 'aws-*', 'lib', '*generated.d.ts'),
42
+ `!${path.join(cdkDir, 'aws-*', 'lib', 'index.d.ts')}`,
43
+ ];
44
+ const paths = await globby(globPaths);
45
+ const nestedResourceNames = await Promise.all(
46
+ paths.map((file) => fileToTaggableResourceNames(file))
47
+ );
48
+ return nestedResourceNames.flat();
49
+ };
50
+
51
+ const getResourceMap = async () => {
52
+ const cacheFile = path.join(cachePath, 'cft_taggable_resource_map.json');
53
+ let resTagMap;
54
+ resTagMap = await getCachedFile(cacheFile);
55
+ if (resTagMap) {
56
+ return resTagMap;
57
+ }
58
+ // note: should be called during prepublishing only
59
+ resTagMap = {};
60
+ const resources = await getResources();
61
+ // use cached map
62
+ if (Object.keys(resTagMap).length > 0) {
63
+ return resTagMap;
64
+ }
65
+
66
+ for (const resName of resources) {
67
+ resTagMap[resName] = true;
68
+ }
69
+ // cache it
70
+ await mkCacheDir();
71
+ await fs.writeFile(cacheFile, JSON.stringify(resTagMap, null, 2));
72
+ return resTagMap;
73
+ };
74
+
75
+ const isTaggableResource = async (resourceName) => {
76
+ const resMp = await getResourceMap();
77
+ return Boolean(resMp[resourceName]);
78
+ };
79
+
80
+ /*
81
+ * @param {string} file
82
+ * @returns {Promise<string[]>}
83
+ *
84
+ * Matches comments of an aws-* resource file d.ts file definition
85
+ * looking for @cloudformationResource and ITaggable
86
+ */
87
+ async function fileToTaggableResourceNames(file) {
88
+ const taggableResources = [];
89
+ const data = (await fs.readFile(file)).toString();
90
+ // console.log(file);
91
+ const matches = [...data.matchAll(/.*@cloudformationResource\s(.*)(\s.*){4}(ITaggable)/gm)];
92
+ if (matches?.length >= 0) {
93
+ for (const match of matches) {
94
+ taggableResources.push(match[1]);
95
+ }
96
+ }
97
+ return taggableResources;
98
+ }
99
+
100
+ /*
101
+ * @param {string} cacheFile
102
+ * @returns {Promise<T>}
103
+ *
104
+ */
105
+ async function getCachedFile(cacheFile) {
106
+ try {
107
+ const data = await fs.readFile(cacheFile);
108
+ const cached = JSON.parse(data.toString());
109
+ if (cached && cached.length > 0) {
110
+ return cached;
111
+ }
112
+ } catch (e) {
113
+ // do nothing
114
+ }
115
+ }
116
+
117
+ async function mkCacheDir() {
118
+ try {
119
+ await fs.access(cachePath);
120
+ } catch (e) {
121
+ try {
122
+ await fs.mkdir(cachePath, { recursive: true });
123
+ } catch (e2) {
124
+ // --force
125
+ }
126
+ }
127
+ }
128
+
129
+ module.exports = {
130
+ getResources,
131
+ getResourceMap,
132
+ isTaggableResource,
133
+ };
@@ -0,0 +1,18 @@
1
+ const taggable = require('./taggable');
2
+ const assert = require('assert');
3
+
4
+ describe(taggable.isTaggableResource.name, () => {
5
+ ['AWS::IAM::Role'].forEach((resourceName) => {
6
+ it(`${resourceName} is taggable`, async () => {
7
+ const isTag = await taggable.isTaggableResource(resourceName);
8
+ assert.ok(isTag);
9
+ });
10
+ });
11
+
12
+ ['AWS::IAM::Policy', 'AWS::IAM::ManagedPolicy'].forEach((resourceName) => {
13
+ it(`${resourceName} is NOT taggable`, async () => {
14
+ const isTag = await taggable.isTaggableResource(resourceName);
15
+ assert.ok(!isTag);
16
+ });
17
+ });
18
+ });