@znemz/cfn-include 2.1.23 → 4.0.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/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +196 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +697 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/cache.d.ts +12 -0
- package/dist/lib/cache.d.ts.map +1 -0
- package/{lib → dist/lib}/cache.js +8 -13
- package/dist/lib/cache.js.map +1 -0
- package/dist/lib/cfnclient.d.ts +17 -0
- package/dist/lib/cfnclient.d.ts.map +1 -0
- package/dist/lib/cfnclient.js +69 -0
- package/dist/lib/cfnclient.js.map +1 -0
- package/dist/lib/include/query.d.ts +4 -0
- package/dist/lib/include/query.d.ts.map +1 -0
- package/dist/lib/include/query.js +14 -0
- package/dist/lib/include/query.js.map +1 -0
- package/dist/lib/internals.d.ts +10 -0
- package/dist/lib/internals.d.ts.map +1 -0
- package/dist/lib/internals.js +145 -0
- package/dist/lib/internals.js.map +1 -0
- package/dist/lib/parselocation.d.ts +3 -0
- package/dist/lib/parselocation.d.ts.map +1 -0
- package/dist/lib/parselocation.js +19 -0
- package/dist/lib/parselocation.js.map +1 -0
- package/dist/lib/promise-utils.d.ts +19 -0
- package/dist/lib/promise-utils.d.ts.map +1 -0
- package/dist/lib/promise-utils.js +36 -0
- package/dist/lib/promise-utils.js.map +1 -0
- package/dist/lib/promise.d.ts +6 -0
- package/dist/lib/promise.d.ts.map +1 -0
- package/dist/lib/promise.js +16 -0
- package/dist/lib/promise.js.map +1 -0
- package/dist/lib/replaceEnv.d.ts +8 -0
- package/dist/lib/replaceEnv.d.ts.map +1 -0
- package/dist/lib/replaceEnv.js +48 -0
- package/dist/lib/replaceEnv.js.map +1 -0
- package/dist/lib/request.d.ts +2 -0
- package/dist/lib/request.d.ts.map +1 -0
- package/dist/lib/request.js +19 -0
- package/dist/lib/request.js.map +1 -0
- package/dist/lib/schema.d.ts +9 -0
- package/dist/lib/schema.d.ts.map +1 -0
- package/dist/lib/schema.js +115 -0
- package/dist/lib/schema.js.map +1 -0
- package/dist/lib/scope.d.ts +23 -0
- package/dist/lib/scope.d.ts.map +1 -0
- package/dist/lib/scope.js +36 -0
- package/dist/lib/scope.js.map +1 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +13 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/lib/yaml.d.ts +8 -0
- package/dist/lib/yaml.d.ts.map +1 -0
- package/dist/lib/yaml.js +74 -0
- package/dist/lib/yaml.js.map +1 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +6 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/options.d.ts +88 -0
- package/dist/types/options.d.ts.map +1 -0
- package/dist/types/options.js +5 -0
- package/dist/types/options.js.map +1 -0
- package/dist/types/template.d.ts +290 -0
- package/dist/types/template.d.ts.map +1 -0
- package/dist/types/template.js +5 -0
- package/dist/types/template.js.map +1 -0
- package/package.json +23 -10
- package/bin/cli.js +0 -204
- package/index.js +0 -899
- package/lib/cfnclient.js +0 -84
- package/lib/include/api.js +0 -13
- package/lib/include/query.js +0 -16
- package/lib/internals.js +0 -171
- package/lib/parselocation.js +0 -15
- package/lib/promise-utils.js +0 -46
- package/lib/promise.js +0 -18
- package/lib/replaceEnv.js +0 -66
- package/lib/request.js +0 -22
- package/lib/schema.js +0 -117
- package/lib/scope.js +0 -52
- package/lib/utils.js +0 -16
- package/lib/yaml.js +0 -76
package/index.js
DELETED
|
@@ -1,899 +0,0 @@
|
|
|
1
|
-
import url from 'node:url';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import _ from 'lodash';
|
|
4
|
-
import { glob } from 'glob';
|
|
5
|
-
import sortObject from '@znemz/sort-object';
|
|
6
|
-
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
|
|
7
|
-
import { addProxyToClient } from 'aws-sdk-v3-proxy';
|
|
8
|
-
|
|
9
|
-
// path.parse is native in Node.js - no need for path-parse package
|
|
10
|
-
import deepMerge from 'deepmerge';
|
|
11
|
-
import { isTaggableResource } from '@znemz/cft-utils/src/resources/taggable.js';
|
|
12
|
-
|
|
13
|
-
import request from './lib/request.js';
|
|
14
|
-
import * as PromiseExt from './lib/promise.js';
|
|
15
|
-
|
|
16
|
-
const S3 = (opts = {}) => addProxyToClient(new S3Client(opts), { throwOnNoProxy: false });
|
|
17
|
-
|
|
18
|
-
const s3 = S3();
|
|
19
|
-
import * as yaml from './lib/yaml.js';
|
|
20
|
-
import { getParser } from './lib/include/query.js';
|
|
21
|
-
import parseLocation from './lib/parselocation.js';
|
|
22
|
-
import replaceEnv from './lib/replaceEnv.js';
|
|
23
|
-
|
|
24
|
-
import { lowerCamelCase, upperCamelCase } from './lib/utils.js';
|
|
25
|
-
import { isOurExplicitFunction } from './lib/schema.js';
|
|
26
|
-
import { getAwsPseudoParameters, buildResourceArn } from './lib/internals.js';
|
|
27
|
-
import { cachedReadFile } from './lib/cache.js';
|
|
28
|
-
import { createChildScope } from './lib/scope.js';
|
|
29
|
-
import { promiseProps } from './lib/promise-utils.js';
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* @param {object} options
|
|
33
|
-
* @param {object} [options.template] JSON|Yaml Object Document
|
|
34
|
-
* optional and can be derived from url
|
|
35
|
-
* @param {string} [options.url] '(file|s3):///SOME_FILE_PATH.(json|yaml)'
|
|
36
|
-
* @param {boolean} options.doEnv inject environment from process.env as well
|
|
37
|
-
* @param {boolean} options.doEval allow Fn::Eval to be used (js eval)
|
|
38
|
-
* @param {Object.<string, string>} [options.inject] object to
|
|
39
|
-
* inject { KEY: Value } from where ${KEY}
|
|
40
|
-
* is substituted with Value
|
|
41
|
-
* @param {boolean} [options.doLog] log all arguments at the include recurse level
|
|
42
|
-
* @param {Array<string>} [options.refNowIgnores] array of logical resource IDs to ignore when resolving Fn::RefNow references
|
|
43
|
-
* @param {boolean} [options.refNowIgnoreMissing] if true, return Ref syntax for unresolvable references; if false, throw error
|
|
44
|
-
* @param {object} [options.rootTemplate] the root template object for resource lookups in Fn::RefNow
|
|
45
|
-
*
|
|
46
|
-
* Example: Load off off file system
|
|
47
|
-
* include({
|
|
48
|
-
* template: yaml.load(template),
|
|
49
|
-
* url: `file://${location}`,
|
|
50
|
-
* doEnv: opts.enable === 'env',
|
|
51
|
-
* inject: opts.inject,
|
|
52
|
-
* doLog: opts.doLog,
|
|
53
|
-
* doEval: opts.doEval, -- allow Fn::Eval to be used
|
|
54
|
-
* })
|
|
55
|
-
*/
|
|
56
|
-
export default async function (options) {
|
|
57
|
-
let { template } = options;
|
|
58
|
-
options.doEnv = getBoolEnvOpt(options.doEnv, 'CFN_INCLUDE_DO_ENV');
|
|
59
|
-
options.doEval = getBoolEnvOpt(options.doEval, 'CFN_INCLUDE_DO_EVAL');
|
|
60
|
-
|
|
61
|
-
const base = parseLocation(options.url);
|
|
62
|
-
const scope = options.scope || {};
|
|
63
|
-
if (base.relative) throw new Error('url cannot be relative');
|
|
64
|
-
template = !template
|
|
65
|
-
? fnInclude({ base, scope, cft: options.url, ...options })
|
|
66
|
-
: template;
|
|
67
|
-
// Resolve template if it's a promise to extract the root template for reference lookups
|
|
68
|
-
const resolvedTemplate = await Promise.resolve(template);
|
|
69
|
-
return recurse({
|
|
70
|
-
base, scope,
|
|
71
|
-
cft: resolvedTemplate,
|
|
72
|
-
rootTemplate: resolvedTemplate,
|
|
73
|
-
...options,
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* @param {object} base file options
|
|
79
|
-
* @param {string} base.protocol 'file' | 's3
|
|
80
|
-
* @param {string} [base.host] url to download file from
|
|
81
|
-
* @param {string} base.path 'SOME_FILE_PATH.(json|yaml)',
|
|
82
|
-
* @param {boolean} [base.relative] is the file path relative?
|
|
83
|
-
* @param {string} [base.raw] '(file|s3):///SOME_FILE_PATH.(json|yaml)'
|
|
84
|
-
* @param {object} [scope] initally {} and optional eventually defined
|
|
85
|
-
* derrived in Fn::Map for template find and replace
|
|
86
|
-
* IE:
|
|
87
|
-
* TEMPLATE:
|
|
88
|
-
* {
|
|
89
|
-
* "Fn::Map": [
|
|
90
|
-
* [1, 2], {
|
|
91
|
-
* "Value": "_"
|
|
92
|
-
* }
|
|
93
|
-
* ]
|
|
94
|
-
*}
|
|
95
|
-
* yields:
|
|
96
|
-
* { scope: { _: 1 } }
|
|
97
|
-
* @param {Document|String|Promise<DocumentString>} cft
|
|
98
|
-
* Document object is recursed at every Field level and eventually reduced to a string
|
|
99
|
-
* @param {Object} opts
|
|
100
|
-
* @param {boolean} opts.doEnv inject environment from process.env as well
|
|
101
|
-
* @param {Object} opts.inject object to inject { KEY: Value } from where ${KEY}
|
|
102
|
-
* is subtituted with Value
|
|
103
|
-
* @param {boolean} opts.doLog log all arguments at the include recurse level
|
|
104
|
-
* @param {Array<string>} [opts.refNowIgnores] array of logical resource IDs to ignore when resolving Fn::RefNow references
|
|
105
|
-
* @param {boolean} [opts.refNowIgnoreMissing] if true, return Ref syntax for unresolvable references; if false, throw error
|
|
106
|
-
* @param {object} [rootTemplate] the root template object for resource lookups in Fn::RefNow
|
|
107
|
-
* @param {String} [caller] internal use only for logging, to aid in who called recurse Fn::Include, Fn::RefNow, etc..
|
|
108
|
-
*/
|
|
109
|
-
async function recurse({ base, scope, cft, rootTemplate, caller, ...opts }) {
|
|
110
|
-
if (opts.doLog) {
|
|
111
|
-
console.log({ base, scope, cft, rootTemplate, caller, ...opts });
|
|
112
|
-
}
|
|
113
|
-
// Use Object.create() for O(1) child scope creation instead of O(n) clone
|
|
114
|
-
scope = createChildScope(scope);
|
|
115
|
-
if (Array.isArray(cft)) {
|
|
116
|
-
return Promise.all(cft.map((o) => recurse({ base, scope, cft: o, rootTemplate, caller: 'recurse:isArray', ...opts })));
|
|
117
|
-
}
|
|
118
|
-
if (_.isPlainObject(cft)) {
|
|
119
|
-
if (cft['Fn::Map']) {
|
|
120
|
-
const args = cft['Fn::Map'];
|
|
121
|
-
const [list] = args;
|
|
122
|
-
const body = args[args.length - 1];
|
|
123
|
-
let placeholder = args[1],
|
|
124
|
-
idx,
|
|
125
|
-
sz,
|
|
126
|
-
hasindex = false,
|
|
127
|
-
hassize = false;
|
|
128
|
-
if (Array.isArray(placeholder)) {
|
|
129
|
-
// multiple placeholders
|
|
130
|
-
idx = placeholder[1];
|
|
131
|
-
hasindex = true;
|
|
132
|
-
if (placeholder.length > 2) {
|
|
133
|
-
sz = placeholder[2];
|
|
134
|
-
hassize = true;
|
|
135
|
-
}
|
|
136
|
-
placeholder = placeholder[0];
|
|
137
|
-
}
|
|
138
|
-
if (args.length === 2) {
|
|
139
|
-
placeholder = '_';
|
|
140
|
-
}
|
|
141
|
-
return PromiseExt.mapX(recurse({ base, scope, cft: list, rootTemplate, caller: 'Fn::Map', ...opts }), (replace, key) => {
|
|
142
|
-
// Use Object.create() for O(1) child scope creation instead of O(n) clone
|
|
143
|
-
const additions = { [placeholder]: replace };
|
|
144
|
-
if (hasindex) {
|
|
145
|
-
additions[idx] = key;
|
|
146
|
-
}
|
|
147
|
-
const childScope = createChildScope(scope, additions);
|
|
148
|
-
const replaced = findAndReplace(childScope, _.cloneDeep(body));
|
|
149
|
-
return recurse({ base, scope: childScope, cft: replaced, rootTemplate, caller: 'Fn::Map', ...opts });
|
|
150
|
-
}).then((_cft) => {
|
|
151
|
-
if (hassize) {
|
|
152
|
-
_cft = findAndReplace({ [sz]: _cft.length }, _cft);
|
|
153
|
-
}
|
|
154
|
-
return recurse({ base, scope, cft: _cft, rootTemplate, caller: 'Fn::Map', ...opts });
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
if (cft['Fn::Length']) {
|
|
158
|
-
if (Array.isArray(cft['Fn::Length'])) {
|
|
159
|
-
return cft['Fn::Length'].length;
|
|
160
|
-
}
|
|
161
|
-
return recurse({ base, scope, cft: cft['Fn::Length'], rootTemplate, caller: 'Fn::Length', ...opts }).then((x) => {
|
|
162
|
-
if (Array.isArray(x)) {
|
|
163
|
-
return x.length;
|
|
164
|
-
}
|
|
165
|
-
return 0;
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
if (cft['Fn::Include']) {
|
|
169
|
-
return fnInclude({ base, scope, cft: cft['Fn::Include'], ...opts })
|
|
170
|
-
.then(function (json) {
|
|
171
|
-
if (!_.isPlainObject(json)) return json;
|
|
172
|
-
delete cft['Fn::Include'];
|
|
173
|
-
_.defaults(cft, json);
|
|
174
|
-
return cft;
|
|
175
|
-
})
|
|
176
|
-
.then((_cft) => findAndReplace(scope, _cft))
|
|
177
|
-
.then((t) => recurse({ base, scope, cft: t, rootTemplate, caller: 'Fn::Include', ...opts }));
|
|
178
|
-
}
|
|
179
|
-
if (cft['Fn::Flatten']) {
|
|
180
|
-
return recurse({ base, scope, cft: cft['Fn::Flatten'], rootTemplate, caller: 'Fn::Flatten', ...opts }).then(function (json) {
|
|
181
|
-
return json.flat();
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
if (cft['Fn::FlattenDeep']) {
|
|
185
|
-
return recurse({ base, scope, cft: cft['Fn::FlattenDeep'], rootTemplate, caller: 'Fn::FlattenDeep', ...opts }).then(
|
|
186
|
-
function (json) {
|
|
187
|
-
return json.flat(Infinity);
|
|
188
|
-
},
|
|
189
|
-
);
|
|
190
|
-
}
|
|
191
|
-
if (cft['Fn::Uniq']) {
|
|
192
|
-
return recurse({ base, scope, cft: cft['Fn::Uniq'], rootTemplate, caller: 'Fn::Uniq', ...opts }).then(function (json) {
|
|
193
|
-
return [...new Set(json)];
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
if (cft['Fn::Compact']) {
|
|
197
|
-
return recurse({ base, scope, cft: cft['Fn::Compact'], rootTemplate, caller: 'Fn::Compact', ...opts }).then(function (json) {
|
|
198
|
-
return json.filter(Boolean);
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
if (cft['Fn::Concat']) {
|
|
202
|
-
return recurse({ base, scope, cft: cft['Fn::Concat'], rootTemplate, caller: 'Fn::Concat', ...opts }).then(function (json) {
|
|
203
|
-
return _.concat(...json);
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
if (cft['Fn::Sort']) {
|
|
207
|
-
return recurse({ base, scope, cft: cft['Fn::Sort'], rootTemplate, caller: 'Fn::Sort', ...opts }).then(function (array) {
|
|
208
|
-
return array.sort();
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
if (cft['Fn::SortedUniq']) {
|
|
212
|
-
return recurse({ base, scope, cft: cft['Fn::SortedUniq'], rootTemplate, caller: 'Fn::SortedUniq', ...opts }).then(
|
|
213
|
-
function (array) {
|
|
214
|
-
return _.sortedUniq(array.sort());
|
|
215
|
-
},
|
|
216
|
-
);
|
|
217
|
-
}
|
|
218
|
-
if (cft['Fn::SortBy']) {
|
|
219
|
-
return recurse({ base, scope, cft: cft['Fn::SortBy'], rootTemplate, caller: 'Fn::SortBy', ...opts }).then(function ({
|
|
220
|
-
list,
|
|
221
|
-
iteratees,
|
|
222
|
-
}) {
|
|
223
|
-
return _.sortBy(list, iteratees);
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
if (cft['Fn::SortObject']) {
|
|
227
|
-
return recurse({ base, scope, cft: cft['Fn::SortObject'], rootTemplate, caller: 'Fn::SortObject', ...opts }).then(function ({
|
|
228
|
-
|
|
229
|
-
object,
|
|
230
|
-
options,
|
|
231
|
-
...rest // allow object to be optional (implied)
|
|
232
|
-
}) {
|
|
233
|
-
return sortObject(object || rest, options);
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
if (cft['Fn::Without']) {
|
|
237
|
-
return recurse({ base, scope, cft: cft['Fn::Without'], rootTemplate, caller: 'Fn::Without', ...opts }).then(function (json) {
|
|
238
|
-
json = Array.isArray(json) ? { list: json[0], withouts: json[1] } : json;
|
|
239
|
-
return _.without(json.list, ...json.withouts);
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
if (cft['Fn::Omit']) {
|
|
243
|
-
return recurse({ base, scope, cft: cft['Fn::Omit'], rootTemplate, caller: 'Fn::Omit', ...opts }).then(function (json) {
|
|
244
|
-
json = Array.isArray(json) ? { object: json[0], omits: json[1] } : json;
|
|
245
|
-
return _.omit(json.object, json.omits);
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
if (cft['Fn::OmitEmpty']) {
|
|
249
|
-
return recurse({ base, scope, cft: cft['Fn::OmitEmpty'], rootTemplate, caller: 'Fn::OmitEmpty', ...opts }).then(
|
|
250
|
-
function (json) {
|
|
251
|
-
// omit falsy values except false, and 0
|
|
252
|
-
return _.omitBy(json, (v) => !v && v !== false && v !== 0);
|
|
253
|
-
},
|
|
254
|
-
);
|
|
255
|
-
}
|
|
256
|
-
if (cft['Fn::Eval']) {
|
|
257
|
-
if (!opts.doEval) {
|
|
258
|
-
return Promise.reject(new Error('Fn::Eval is not allowed doEval is falsy'));
|
|
259
|
-
}
|
|
260
|
-
return recurse({ base, scope, cft: cft['Fn::Eval'], rootTemplate, caller: 'Fn::Eval', ...opts }).then(function (json) {
|
|
261
|
-
// **WARNING** you have now enabled god mode
|
|
262
|
-
|
|
263
|
-
let { state, script, inject, doLog } = json;
|
|
264
|
-
script = replaceEnv(script, _.merge(_.cloneDeep(opts.inject), inject), opts.doEnv);
|
|
265
|
-
if (doLog) {
|
|
266
|
-
|
|
267
|
-
console.log({ state, script, inject });
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
return eval(script);
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
if (cft['Fn::Filenames']) {
|
|
274
|
-
return recurse({ base, scope, cft: cft['Fn::Filenames'], rootTemplate, caller: 'Fn::Filenames', ...opts }).then(
|
|
275
|
-
async function (json) {
|
|
276
|
-
json = _.isPlainObject(json) ? { ...json } : { location: json };
|
|
277
|
-
if (json.doLog) {
|
|
278
|
-
|
|
279
|
-
console.log(json);
|
|
280
|
-
}
|
|
281
|
-
const location = parseLocation(json.location);
|
|
282
|
-
|
|
283
|
-
if (!_.isEmpty(location) && !location.protocol) {
|
|
284
|
-
location.protocol = base.protocol;
|
|
285
|
-
}
|
|
286
|
-
if (location.protocol === 'file') {
|
|
287
|
-
const absolute = location.relative
|
|
288
|
-
? path.join(path.dirname(base.path), location.host, location.path || '')
|
|
289
|
-
: [location.host, location.path].join('');
|
|
290
|
-
const globs = (await glob(absolute)).sort();
|
|
291
|
-
if (json.omitExtension) {
|
|
292
|
-
return globs.map((f) => path.basename(f, path.extname(f)));
|
|
293
|
-
}
|
|
294
|
-
return globs;
|
|
295
|
-
}
|
|
296
|
-
return 'Unsupported File Type';
|
|
297
|
-
},
|
|
298
|
-
);
|
|
299
|
-
}
|
|
300
|
-
if (cft['Fn::Merge']) {
|
|
301
|
-
return recurse({ base, scope, cft: cft['Fn::Merge'], rootTemplate, caller: 'Fn::Merge', ...opts }).then(function (json) {
|
|
302
|
-
delete cft['Fn::Merge'];
|
|
303
|
-
|
|
304
|
-
return recurse({ base, scope, cft: _.defaults(cft, _.merge.apply(_, json)), rootTemplate, caller: 'Fn::Merge', ...opts });
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
if (cft['Fn::DeepMerge']) {
|
|
308
|
-
return recurse({ base, scope, cft: cft['Fn::DeepMerge'], rootTemplate, caller: 'Fn::DeepMerge', ...opts }).then(
|
|
309
|
-
function (json) {
|
|
310
|
-
delete cft['Fn::DeepMerge'];
|
|
311
|
-
let mergedObj = {};
|
|
312
|
-
if (json && json.length) {
|
|
313
|
-
for (const j of json) {
|
|
314
|
-
mergedObj = deepMerge(mergedObj, j);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
return recurse({ base, scope, cft: _.defaults(cft, mergedObj), rootTemplate, caller: 'Fn::DeepMerge', ...opts });
|
|
318
|
-
},
|
|
319
|
-
);
|
|
320
|
-
}
|
|
321
|
-
if (cft['Fn::ObjectKeys']) {
|
|
322
|
-
return recurse({ base, scope, cft: cft['Fn::ObjectKeys'], rootTemplate, caller: 'Fn::ObjectKeys', ...opts }).then((json) =>
|
|
323
|
-
Object.keys(json),
|
|
324
|
-
);
|
|
325
|
-
}
|
|
326
|
-
if (cft['Fn::ObjectValues']) {
|
|
327
|
-
return recurse({ base, scope, cft: cft['Fn::ObjectValues'], rootTemplate, caller: 'Fn::ObjectValues', ...opts }).then((json) =>
|
|
328
|
-
Object.values(json),
|
|
329
|
-
);
|
|
330
|
-
}
|
|
331
|
-
if (cft['Fn::Stringify']) {
|
|
332
|
-
return recurse({ base, scope, cft: cft['Fn::Stringify'], rootTemplate, caller: 'Fn::Stringify', ...opts }).then(
|
|
333
|
-
function (json) {
|
|
334
|
-
return JSON.stringify(json);
|
|
335
|
-
},
|
|
336
|
-
);
|
|
337
|
-
}
|
|
338
|
-
if (cft['Fn::StringSplit']) {
|
|
339
|
-
return recurse({ base, scope, cft: cft['Fn::StringSplit'], rootTemplate, caller: 'Fn::StringSplit', ...opts }).then(
|
|
340
|
-
({ string, separator, doLog }) => {
|
|
341
|
-
if (!string) {
|
|
342
|
-
string = '';
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
if (doLog) console.log({ string, separator });
|
|
346
|
-
if (!separator) {
|
|
347
|
-
separator = ',';
|
|
348
|
-
}
|
|
349
|
-
return string.split(separator);
|
|
350
|
-
},
|
|
351
|
-
);
|
|
352
|
-
}
|
|
353
|
-
if (cft['Fn::UpperCamelCase']) {
|
|
354
|
-
return upperCamelCase(cft['Fn::UpperCamelCase']);
|
|
355
|
-
}
|
|
356
|
-
if (cft['Fn::LowerCamelCase']) {
|
|
357
|
-
return lowerCamelCase(cft['Fn::LowerCamelCase']);
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
if (cft['Fn::GetEnv']) {
|
|
361
|
-
const args = cft['Fn::GetEnv'];
|
|
362
|
-
if (Array.isArray(args)) {
|
|
363
|
-
const val = process.env[args[0]];
|
|
364
|
-
return val === undefined ? args[1] : val;
|
|
365
|
-
}
|
|
366
|
-
const val = process.env[args];
|
|
367
|
-
if (val === undefined) {
|
|
368
|
-
throw new Error(`environmental variable ${args} is undefined`);
|
|
369
|
-
}
|
|
370
|
-
return val;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
if (cft['Fn::Outputs']) {
|
|
374
|
-
const outputs = await recurse({ base, scope, cft: cft['Fn::Outputs'], caller: 'Fn::Outputs', ...opts });
|
|
375
|
-
const result = {};
|
|
376
|
-
|
|
377
|
-
for (const output in outputs) {
|
|
378
|
-
const val = outputs[output];
|
|
379
|
-
const exp = {
|
|
380
|
-
Export: { Name: { 'Fn::Sub': '${AWS::StackName}:' + output } },
|
|
381
|
-
};
|
|
382
|
-
if (!Array.isArray(val) && typeof val === 'object') {
|
|
383
|
-
result[output] = {
|
|
384
|
-
Value: { 'Fn::Sub': val.Value },
|
|
385
|
-
Condition: val.Condition,
|
|
386
|
-
...exp,
|
|
387
|
-
};
|
|
388
|
-
} else {
|
|
389
|
-
result[output] = {
|
|
390
|
-
Value: { 'Fn::Sub': val },
|
|
391
|
-
...exp,
|
|
392
|
-
};
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
return result;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
if (cft['Fn::Sequence']) {
|
|
399
|
-
const outputs = await recurse({ base, scope, cft: cft['Fn::Sequence'], caller: 'Fn::Sequence', ...opts });
|
|
400
|
-
|
|
401
|
-
let [start, stop, step = 1] = outputs;
|
|
402
|
-
const isString = typeof start === 'string';
|
|
403
|
-
if (isString) {
|
|
404
|
-
start = start.charCodeAt(0);
|
|
405
|
-
stop = stop.charCodeAt(0);
|
|
406
|
-
}
|
|
407
|
-
const seq = Array.from(
|
|
408
|
-
{ length: Math.floor((stop - start) / step) + 1 },
|
|
409
|
-
(__, i) => start + i * step,
|
|
410
|
-
);
|
|
411
|
-
return isString ? seq.map((i) => String.fromCharCode(i)) : seq;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
if (cft['Fn::IfEval']) {
|
|
415
|
-
if (!opts.doEval) {
|
|
416
|
-
return Promise.reject(new Error('Fn::IfEval is not allowed doEval is falsy'));
|
|
417
|
-
}
|
|
418
|
-
return recurse({ base, scope, cft: cft['Fn::IfEval'], rootTemplate, caller: 'Fn::IfEval', ...opts }).then(function (json) {
|
|
419
|
-
|
|
420
|
-
let { truthy, falsy, evalCond, inject, doLog } = json;
|
|
421
|
-
if (!evalCond) {
|
|
422
|
-
return Promise.reject(new Error('Fn::IfEval evalCond is required'));
|
|
423
|
-
}
|
|
424
|
-
evalCond = `(${evalCond})`;
|
|
425
|
-
if (!falsy) {
|
|
426
|
-
falsy = '';
|
|
427
|
-
}
|
|
428
|
-
if (!truthy) {
|
|
429
|
-
truthy = '';
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
evalCond = replaceEnv(evalCond, _.merge(_.cloneDeep(opts.inject), inject), opts.doEnv);
|
|
433
|
-
truthy = replaceEnv(truthy, _.merge(_.cloneDeep(opts.inject), inject), opts.doEnv);
|
|
434
|
-
if (falsy) {
|
|
435
|
-
falsy = replaceEnv(falsy, _.merge(_.cloneDeep(opts.inject), inject), opts.doEnv);
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
const condResult = eval(evalCond);
|
|
440
|
-
|
|
441
|
-
if (doLog) {
|
|
442
|
-
|
|
443
|
-
console.log({ truthy, falsy, inject, evalCond, condResult });
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
if (condResult) {
|
|
447
|
-
return recurse({ base, scope, cft: truthy, rootTemplate, caller: 'Fn::IfEval', ...opts });
|
|
448
|
-
}
|
|
449
|
-
return recurse({ base, scope, cft: falsy, rootTemplate, caller: 'Fn::IfEval', ...opts });
|
|
450
|
-
});
|
|
451
|
-
}
|
|
452
|
-
if (cft['Fn::JoinNow']) {
|
|
453
|
-
return recurse({ base, scope, cft: cft['Fn::JoinNow'], rootTemplate, caller: 'Fn::JoinNow', ...opts }).then((array) => {
|
|
454
|
-
// keeps with same format as Fn::Join ~ more complex
|
|
455
|
-
// vs let [delimitter, ...toJoinArray] = array;
|
|
456
|
-
let [delimitter, toJoinArray] = array;
|
|
457
|
-
delimitter = replaceEnv(delimitter, opts.inject, opts.doEnv);
|
|
458
|
-
return toJoinArray.join(delimitter);
|
|
459
|
-
});
|
|
460
|
-
}
|
|
461
|
-
if (cft['Fn::SubNow']) {
|
|
462
|
-
return recurse({ base, scope, cft: cft['Fn::SubNow'], rootTemplate, caller: 'Fn::SubNow', ...opts }).then((input) => {
|
|
463
|
-
let template = input;
|
|
464
|
-
let variables = {};
|
|
465
|
-
|
|
466
|
-
// Handle both string and [string, variables] formats
|
|
467
|
-
if (Array.isArray(input)) {
|
|
468
|
-
[template, variables] = input;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
// Merge variables with inject and AWS pseudo-parameters
|
|
472
|
-
const allVariables = {
|
|
473
|
-
...getAwsPseudoParameters(),
|
|
474
|
-
...opts.inject,
|
|
475
|
-
...variables,
|
|
476
|
-
};
|
|
477
|
-
|
|
478
|
-
// Perform substitution for ${VarName} pattern
|
|
479
|
-
let result = template.toString();
|
|
480
|
-
_.forEach(allVariables, (value, key) => {
|
|
481
|
-
const regex = new RegExp(`\\$\\{${_.escapeRegExp(key)}\\}`, 'g');
|
|
482
|
-
result = result.replace(regex, String(value));
|
|
483
|
-
});
|
|
484
|
-
|
|
485
|
-
return result;
|
|
486
|
-
});
|
|
487
|
-
}
|
|
488
|
-
if (cft['Fn::RefNow']) {
|
|
489
|
-
// console.log("outter");
|
|
490
|
-
// console.log(opts);
|
|
491
|
-
// Handle both simple string and object format for Fn::RefNow
|
|
492
|
-
// e.g., Fn::RefNow: "MyRole" or Fn::RefNow: { Ref: "MyRole" }
|
|
493
|
-
return recurse({ base, scope, cft: cft['Fn::RefNow'], rootTemplate, caller: 'Fn::RefNow', ...opts }).then((refInput) => {
|
|
494
|
-
// console.log("inner");
|
|
495
|
-
// console.log(opts);
|
|
496
|
-
let refName = refInput;
|
|
497
|
-
let refOptions = {};
|
|
498
|
-
|
|
499
|
-
// Parse the input - could be string or object
|
|
500
|
-
if (_.isPlainObject(refInput)) {
|
|
501
|
-
refName = refInput.Ref || refInput.ref;
|
|
502
|
-
refOptions = _.omit(refInput, ['Ref', 'ref']);
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
// Check if this ref name is in the ignores list
|
|
506
|
-
if (opts.refNowIgnores && opts.refNowIgnores.includes(refName)) {
|
|
507
|
-
return { Ref: refName };
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
// Merge AWS pseudo-parameters with inject and scope variables
|
|
511
|
-
const allRefs = {
|
|
512
|
-
...getAwsPseudoParameters(),
|
|
513
|
-
...process.env,
|
|
514
|
-
...opts.inject,
|
|
515
|
-
...scope,
|
|
516
|
-
};
|
|
517
|
-
|
|
518
|
-
// Try to resolve the reference
|
|
519
|
-
if (refName in allRefs) {
|
|
520
|
-
return allRefs[refName];
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
// Check if this is a LogicalResourceId in the Resources section
|
|
524
|
-
if (rootTemplate && rootTemplate.Resources) {
|
|
525
|
-
const resources = rootTemplate.Resources;
|
|
526
|
-
if (refName in resources) {
|
|
527
|
-
const resource = resources[refName];
|
|
528
|
-
const resourceType = resource.Type;
|
|
529
|
-
const properties = resource.Properties || {};
|
|
530
|
-
|
|
531
|
-
// Determine return type based on key name
|
|
532
|
-
// If the key ends with "Name" (e.g., RoleName, BucketName), return name/identifier
|
|
533
|
-
// Otherwise return ARN (default)
|
|
534
|
-
let returnType = 'arn';
|
|
535
|
-
if (opts.key && opts.key.endsWith('Name')) {
|
|
536
|
-
returnType = 'name';
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
// Try to build an ARN or name for this resource
|
|
540
|
-
// Merge ref options with global options (ref options take precedence)
|
|
541
|
-
const resourceOptions = {
|
|
542
|
-
returnType,
|
|
543
|
-
...opts.refNowReturnType ? { returnType: opts.refNowReturnType } : {},
|
|
544
|
-
...refOptions,
|
|
545
|
-
};
|
|
546
|
-
const result = buildResourceArn(resourceType, properties, allRefs, resourceOptions);
|
|
547
|
-
if (result) {
|
|
548
|
-
return result;
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
// If not found and refNowIgnoreMissing is true, return Ref syntax; otherwise throw error
|
|
554
|
-
if (opts.refNowIgnoreMissing) {
|
|
555
|
-
return { Ref: refName };
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
// Default behavior: throw an error (backward compatible)
|
|
559
|
-
throw new Error(`Unable to resolve Ref for logical name: ${refName}`);
|
|
560
|
-
});
|
|
561
|
-
}
|
|
562
|
-
if (cft['Fn::ApplyTags']) {
|
|
563
|
-
return recurse({ base, scope, cft: cft['Fn::ApplyTags'], rootTemplate, caller: 'Fn::ApplyTags', ...opts }).then((json) => {
|
|
564
|
-
let { tags, Tags, resources } = json;
|
|
565
|
-
tags = tags || Tags; // allow for both caseing
|
|
566
|
-
const promises = [];
|
|
567
|
-
_.each(resources, (val, id) => {
|
|
568
|
-
promises.push(
|
|
569
|
-
isTaggableResource(val.Type).then((isTaggable) => {
|
|
570
|
-
if (isTaggable) {
|
|
571
|
-
resources[id] = deepMerge(
|
|
572
|
-
{
|
|
573
|
-
Properties: {
|
|
574
|
-
Tags: tags,
|
|
575
|
-
},
|
|
576
|
-
},
|
|
577
|
-
val,
|
|
578
|
-
);
|
|
579
|
-
}
|
|
580
|
-
return resources[id];
|
|
581
|
-
}),
|
|
582
|
-
);
|
|
583
|
-
});
|
|
584
|
-
return Promise.all(promises).then(() => resources);
|
|
585
|
-
});
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
return promiseProps(
|
|
589
|
-
_.mapValues(cft, (template, key) => recurse({ base, scope, cft: template, key, rootTemplate, caller: 'recurse:isPlainObject:end', ...opts })),
|
|
590
|
-
);
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
if (cft === undefined) {
|
|
594
|
-
return null;
|
|
595
|
-
}
|
|
596
|
-
return replaceEnv(cft, opts.inject, opts.doEnv);
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
function findAndReplace(scope, object) {
|
|
600
|
-
if (typeof object === 'string') {
|
|
601
|
-
// Use for...in to walk prototype chain (Object.create() based scopes)
|
|
602
|
-
for (const find in scope) {
|
|
603
|
-
if (object === find) {
|
|
604
|
-
object = scope[find];
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
if (typeof object === 'string') {
|
|
609
|
-
// Use for...in to walk prototype chain (Object.create() based scopes)
|
|
610
|
-
for (const find in scope) {
|
|
611
|
-
const replace = scope[find];
|
|
612
|
-
const regex = new RegExp(`\\\${${find}}`, 'g');
|
|
613
|
-
if (find !== '_' && object.match(regex)) {
|
|
614
|
-
object = object.replace(regex, replace);
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
if (Array.isArray(object)) {
|
|
619
|
-
object = object.map((item) => findAndReplace(scope, item));
|
|
620
|
-
} else if (_.isPlainObject(object)) {
|
|
621
|
-
object = _.mapKeys(object, function (value, key) {
|
|
622
|
-
return findAndReplace(scope, key);
|
|
623
|
-
});
|
|
624
|
-
for (const key of Object.keys(object)) {
|
|
625
|
-
if (key === 'Fn::Map') continue;
|
|
626
|
-
object[key] = findAndReplace(scope, object[key]);
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
return object;
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
function interpolate(lines, context) {
|
|
633
|
-
return lines.map(function (line) {
|
|
634
|
-
const parts = [];
|
|
635
|
-
line
|
|
636
|
-
.split(/({{\w+?}})/g)
|
|
637
|
-
.map(function (_line) {
|
|
638
|
-
const match = _line.match(/^{{(\w+)}}$/);
|
|
639
|
-
const value = match ? context[match[1]] : undefined;
|
|
640
|
-
if (!match) return _line;
|
|
641
|
-
if (value === undefined) {
|
|
642
|
-
return '';
|
|
643
|
-
}
|
|
644
|
-
return value;
|
|
645
|
-
})
|
|
646
|
-
.forEach(function (part) {
|
|
647
|
-
const last = parts[parts.length - 1];
|
|
648
|
-
if (_.isPlainObject(part) || _.isPlainObject(last) || !parts.length) {
|
|
649
|
-
parts.push(part);
|
|
650
|
-
} else if (parts.length) {
|
|
651
|
-
parts[parts.length - 1] = last + part;
|
|
652
|
-
}
|
|
653
|
-
});
|
|
654
|
-
return parts.filter(function (part) {
|
|
655
|
-
return part !== '';
|
|
656
|
-
});
|
|
657
|
-
});
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
function fnIncludeOptsFromArray(cft, opts) {
|
|
661
|
-
// Array 1, location, Array 2 Query, Array 3 Optional Parser (default lodash)
|
|
662
|
-
const [location, query, parser = 'lodash'] = cft;
|
|
663
|
-
cft = { location, query, parser, ...opts };
|
|
664
|
-
return cft;
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
function fnIncludeOpts(cft, opts) {
|
|
668
|
-
if (_.isPlainObject(cft)) {
|
|
669
|
-
cft = _.merge(cft, _.cloneDeep(opts));
|
|
670
|
-
} else if (Array.isArray(cft)) {
|
|
671
|
-
cft = fnIncludeOptsFromArray(cft, opts);
|
|
672
|
-
} else {
|
|
673
|
-
// should be string{
|
|
674
|
-
const splits = cft.split('|');
|
|
675
|
-
if (splits.length > 1) {
|
|
676
|
-
cft = fnIncludeOptsFromArray(splits, opts);
|
|
677
|
-
} else {
|
|
678
|
-
cft = { location: cft, ...opts };
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
cft = _.defaults(cft, { type: 'json' });
|
|
683
|
-
return cft;
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
/**
|
|
687
|
-
* Process includes for template documents
|
|
688
|
-
* @param {object} base file options (protocol, host, path, relative, raw)
|
|
689
|
-
* @param {object} scope variable scope for Fn::Map substitutions
|
|
690
|
-
* @param {Document|String|Array} cft include specification (location, query, parser, type, etc.)
|
|
691
|
-
* @param {Object} opts processing options
|
|
692
|
-
* @param {boolean} [opts.doEnv] inject environment from process.env
|
|
693
|
-
* @param {boolean} [opts.doEval] allow Fn::Eval to be used
|
|
694
|
-
* @param {Object} [opts.inject] object to inject { KEY: Value }
|
|
695
|
-
* @param {boolean} [opts.doLog] log all arguments at the include recurse level
|
|
696
|
-
* @param {Array<string>} [opts.refNowIgnores] array of logical resource IDs to ignore when resolving Fn::RefNow references
|
|
697
|
-
* @param {boolean} [opts.refNowIgnoreMissing] if true, return Ref syntax for unresolvable references; if false, throw error
|
|
698
|
-
* @param {object} [opts.rootTemplate] the root template object for resource lookups in Fn::RefNow
|
|
699
|
-
* @returns {Promise} resolved include content
|
|
700
|
-
*/
|
|
701
|
-
async function fnInclude({ base, scope, cft, ...opts }) {
|
|
702
|
-
let procTemplate = async (template, inject = cft.inject, doEnv = opts.doEnv) =>
|
|
703
|
-
replaceEnv(template, inject, doEnv);
|
|
704
|
-
const handleInjectSetup = () => {
|
|
705
|
-
if (cft.inject) {
|
|
706
|
-
const origProcTemplate = procTemplate;
|
|
707
|
-
procTemplate = async (template) => {
|
|
708
|
-
try {
|
|
709
|
-
const inject = await recurse({ base, scope, cft: cft.inject, ...opts });
|
|
710
|
-
const processed = await origProcTemplate(template, inject, opts.doEnv);
|
|
711
|
-
return replaceEnv(processed, inject, opts.doEnv);
|
|
712
|
-
} catch {
|
|
713
|
-
return '';
|
|
714
|
-
}
|
|
715
|
-
};
|
|
716
|
-
}
|
|
717
|
-
};
|
|
718
|
-
handleInjectSetup();
|
|
719
|
-
cft = fnIncludeOpts(cft, opts);
|
|
720
|
-
|
|
721
|
-
if (cft.doLog) {
|
|
722
|
-
console.log({ base, scope, args: cft, ...opts });
|
|
723
|
-
}
|
|
724
|
-
// console.log(args)
|
|
725
|
-
let body;
|
|
726
|
-
let absolute;
|
|
727
|
-
const location = parseLocation(cft.location);
|
|
728
|
-
if (!_.isEmpty(location) && !location.protocol) location.protocol = base.protocol;
|
|
729
|
-
if (location.protocol === 'file') {
|
|
730
|
-
absolute = location.relative
|
|
731
|
-
? path.join(path.dirname(base.path), location.host, location.path || '')
|
|
732
|
-
: [location.host, location.path].join('');
|
|
733
|
-
|
|
734
|
-
// allow script to resolve their own, __dirname via ${CFN_INCLUDE_DIRNAME}
|
|
735
|
-
cft.inject = { CFN_INCLUDE_DIRNAME: path.dirname(absolute), ...cft.inject };
|
|
736
|
-
|
|
737
|
-
handleInjectSetup();
|
|
738
|
-
if (isGlob(cft, absolute)) {
|
|
739
|
-
const paths = (await glob(absolute)).sort();
|
|
740
|
-
const template = yaml.load(paths.map((_p) => `- Fn::Include: file://${_p}`).join('\n'));
|
|
741
|
-
return recurse({ base, scope, cft: template, rootTemplate: template, ...opts });
|
|
742
|
-
}
|
|
743
|
-
body = cachedReadFile(absolute).then(procTemplate);
|
|
744
|
-
absolute = `${location.protocol}://${absolute}`;
|
|
745
|
-
} else if (location.protocol === 's3') {
|
|
746
|
-
const basedir = path.parse(base.path).dir;
|
|
747
|
-
const bucket = location.relative ? base.host : location.host;
|
|
748
|
-
|
|
749
|
-
let key = location.relative ? url.resolve(`${basedir}/`, location.raw) : location.path;
|
|
750
|
-
key = key.replace(/^\//, '');
|
|
751
|
-
absolute = `${location.protocol}://${[bucket, key].join('/')}`;
|
|
752
|
-
body = s3
|
|
753
|
-
.send(
|
|
754
|
-
new GetObjectCommand({
|
|
755
|
-
Bucket: bucket,
|
|
756
|
-
Key: key,
|
|
757
|
-
}),
|
|
758
|
-
)
|
|
759
|
-
.then((res) => res.Body.toString())
|
|
760
|
-
.then(procTemplate);
|
|
761
|
-
} else if (location.protocol && location.protocol.match(/^https?$/)) {
|
|
762
|
-
const basepath = `${path.parse(base.path).dir}/`;
|
|
763
|
-
|
|
764
|
-
absolute = location.relative
|
|
765
|
-
? url.resolve(`${location.protocol}://${base.host}${basepath}`, location.raw)
|
|
766
|
-
: location.raw;
|
|
767
|
-
|
|
768
|
-
body = request(absolute).then(procTemplate);
|
|
769
|
-
}
|
|
770
|
-
return handleIncludeBody({ scope, args: cft, body, absolute });
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
function isGlob(args, str) {
|
|
774
|
-
return args.isGlob || /.*\*/.test(str);
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
/**
|
|
778
|
-
* Process the resolved include body content
|
|
779
|
-
* @param {object} config configuration object
|
|
780
|
-
* @param {object} config.scope variable scope for substitutions
|
|
781
|
-
* @param {Object} config.args include arguments (type, query, parser, inject, doEnv, doEval, doLog, refNowIgnores, refNowIgnoreMissing, rootTemplate)
|
|
782
|
-
* @param {Promise} config.body promise resolving to the file content
|
|
783
|
-
* @param {string} config.absolute absolute path/URL to the included resource
|
|
784
|
-
* @param {string} [config.args.type='json'] type of content to process (json, string, literal)
|
|
785
|
-
* @param {Object|string|Array} [config.args.query] query to apply to the processed template
|
|
786
|
-
* @param {string} [config.args.parser='lodash'] parser to use for querying (lodash, jsonpath)
|
|
787
|
-
* @param {Object} [config.args.context] context object for literal interpolation
|
|
788
|
-
* @param {Object} [config.args.inject] object to inject { KEY: Value }
|
|
789
|
-
* @param {boolean} [config.args.doEnv] inject environment from process.env
|
|
790
|
-
* @param {boolean} [config.args.doEval] allow Fn::Eval to be used
|
|
791
|
-
* @param {boolean} [config.args.doLog] log all arguments at the include recurse level
|
|
792
|
-
* @param {Array<string>} [config.args.refNowIgnores] array of logical resource IDs to ignore when resolving Fn::RefNow references
|
|
793
|
-
* @param {boolean} [config.args.refNowIgnoreMissing] if true, return Ref syntax for unresolvable references; if false, throw error
|
|
794
|
-
*
|
|
795
|
-
* rootTeamplate is not an argument here cause it's loaded via arg.type
|
|
796
|
-
* @returns {Promise} processed template content
|
|
797
|
-
*/
|
|
798
|
-
async function handleIncludeBody({ scope, args, body, absolute }) {
|
|
799
|
-
const procTemplate = (temp) => replaceEnv(temp, args.inject, args.doEnv);
|
|
800
|
-
try {
|
|
801
|
-
switch (args.type) {
|
|
802
|
-
case 'json': {
|
|
803
|
-
let b = await body;
|
|
804
|
-
b = procTemplate(b);
|
|
805
|
-
const rootTemplate = yaml.load(b);
|
|
806
|
-
const caller = 'handleIncludeBody:json';
|
|
807
|
-
// recurse all the way down and work your way out
|
|
808
|
-
const loopTemplate = (temp) => {
|
|
809
|
-
return recurse({
|
|
810
|
-
base: parseLocation(absolute),
|
|
811
|
-
scope,
|
|
812
|
-
cft: temp,
|
|
813
|
-
caller,
|
|
814
|
-
rootTemplate,
|
|
815
|
-
doEnv: args.doEnv,
|
|
816
|
-
doEval: args.doEval,
|
|
817
|
-
doLog: args.doLog,
|
|
818
|
-
inject: args.inject,
|
|
819
|
-
refNowIgnores: args.refNowIgnores,
|
|
820
|
-
refNowIgnoreMissing: args.refNowIgnoreMissing, // note we can't use ...args here because of circular ref
|
|
821
|
-
}).then((_temp) => {
|
|
822
|
-
if (!_temp || !Object.keys(_temp).length) {
|
|
823
|
-
return _temp;
|
|
824
|
-
}
|
|
825
|
-
// ONLY RECURSE IF it is one of our funcs.
|
|
826
|
-
if (isOurExplicitFunction(Object.keys(_temp)[0])) {
|
|
827
|
-
return loopTemplate(_temp);
|
|
828
|
-
}
|
|
829
|
-
return _temp;
|
|
830
|
-
});
|
|
831
|
-
};
|
|
832
|
-
|
|
833
|
-
return loopTemplate(rootTemplate).then(async (temp) => {
|
|
834
|
-
if (!args.query) {
|
|
835
|
-
return temp;
|
|
836
|
-
}
|
|
837
|
-
// once fully recursed we can query the resultant template
|
|
838
|
-
const query = typeof args.query === 'string'
|
|
839
|
-
? replaceEnv(args.query, args.inject, args.doEnv)
|
|
840
|
-
: await recurse({
|
|
841
|
-
base: parseLocation(absolute),
|
|
842
|
-
scope,
|
|
843
|
-
cft: args.query,
|
|
844
|
-
caller,
|
|
845
|
-
rootTemplate,
|
|
846
|
-
doEnv: args.doEnv,
|
|
847
|
-
doLog: args.doLog,
|
|
848
|
-
inject: args.inject,
|
|
849
|
-
refNowIgnores: args.refNowIgnores,
|
|
850
|
-
refNowIgnoreMissing: args.refNowIgnoreMissing,
|
|
851
|
-
});
|
|
852
|
-
return getParser(args.parser)(temp, query);
|
|
853
|
-
});
|
|
854
|
-
}
|
|
855
|
-
case 'string': {
|
|
856
|
-
const template = await body;
|
|
857
|
-
return procTemplate(template);
|
|
858
|
-
}
|
|
859
|
-
case 'literal': {
|
|
860
|
-
return body.then(function (template) {
|
|
861
|
-
template = procTemplate(template);
|
|
862
|
-
let lines = JSONifyString(template);
|
|
863
|
-
if (_.isPlainObject(args.context)) {
|
|
864
|
-
lines = interpolate(lines, args.context);
|
|
865
|
-
}
|
|
866
|
-
return {
|
|
867
|
-
'Fn::Join': ['', lines.flat()],
|
|
868
|
-
};
|
|
869
|
-
});
|
|
870
|
-
}
|
|
871
|
-
default:
|
|
872
|
-
throw new Error(`Unknown template type to process type: ${args.type}.`);
|
|
873
|
-
}
|
|
874
|
-
} catch (e) {
|
|
875
|
-
// if the location matches replaceEnv.IsRegExVar then swallow error and retun ''
|
|
876
|
-
if ((replaceEnv.IsRegExVar(absolute) && args.ignoreMissingVar) || args.ignoreMissingFile) {
|
|
877
|
-
return '';
|
|
878
|
-
}
|
|
879
|
-
throw e;
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
function JSONifyString(string) {
|
|
884
|
-
const lines = [];
|
|
885
|
-
const split = string.toString().split(/(\r?\n)/);
|
|
886
|
-
for (let idx = 0; idx < split.length; idx++) {
|
|
887
|
-
const line = split[idx];
|
|
888
|
-
if (idx % 2) {
|
|
889
|
-
lines[(idx - 1) / 2] = lines[(idx - 1) / 2] + line;
|
|
890
|
-
} else {
|
|
891
|
-
lines.push(line);
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
return lines;
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
function getBoolEnvOpt(opt, envKey) {
|
|
898
|
-
return process.env[envKey] ? !!process.env[envKey] : opt;
|
|
899
|
-
}
|