@znemz/cfn-include 2.1.12 → 2.1.13
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/README.md +119 -1
- package/index.js +114 -225
- package/lib/internals.js +174 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -93,7 +93,9 @@ Options:
|
|
|
93
93
|
* `--enable` different options / toggles: ['env','eval'] [string] [choices: 'env','eval','env.eval' etc...]
|
|
94
94
|
* `env` pre-process env vars and inject into templates as they are processed looks for $KEY or ${KEY} matches
|
|
95
95
|
* `-i, --inject` JSON string payload to use for template injection. (Takes precedence over process.env (if enabled) injection and will be merged on top of process.env)
|
|
96
|
-
* `--doLog` console log out include options in recurse step.
|
|
96
|
+
* `--doLog` console log out include options in recurse step. Shows caller parameter to aid debugging nested function calls.
|
|
97
|
+
* `--ref-now-ignore-missing` do not fail if `Fn::RefNow` reference cannot be resolved; instead return in standard CloudFormation `Ref` syntax
|
|
98
|
+
* `--ref-now-ignores <names>` comma-separated list of reference names to ignore if not found (e.g., `OptionalRef1,OptionalRef2`)
|
|
97
99
|
`cfn-include` also accepts a template passed from stdin
|
|
98
100
|
|
|
99
101
|
```
|
|
@@ -1370,6 +1372,122 @@ Options are query parameters.
|
|
|
1370
1372
|
|
|
1371
1373
|
- `validate=false` do not validate template [true]
|
|
1372
1374
|
|
|
1375
|
+
## Developer Documentation
|
|
1376
|
+
|
|
1377
|
+
### Debug Logging with doLog
|
|
1378
|
+
|
|
1379
|
+
The `cfn-include` preprocessor supports comprehensive debug logging for tracing template processing. When enabled, the `doLog` option logs all arguments at each recursion level in the template processing pipeline.
|
|
1380
|
+
|
|
1381
|
+
#### Enabling doLog
|
|
1382
|
+
|
|
1383
|
+
**CLI:**
|
|
1384
|
+
```bash
|
|
1385
|
+
cfn-include template.yaml --doLog
|
|
1386
|
+
```
|
|
1387
|
+
|
|
1388
|
+
**Programmatic:**
|
|
1389
|
+
```javascript
|
|
1390
|
+
include({
|
|
1391
|
+
template: myTemplate,
|
|
1392
|
+
url: 'file:///path/to/template.yaml',
|
|
1393
|
+
doLog: true
|
|
1394
|
+
})
|
|
1395
|
+
```
|
|
1396
|
+
|
|
1397
|
+
#### Understanding Caller Parameter
|
|
1398
|
+
|
|
1399
|
+
The `recurse` function now includes a `caller` parameter to help identify which function triggered each recursion step. This is invaluable for debugging complex templates with nested function calls. The caller parameter provides a trace path like:
|
|
1400
|
+
|
|
1401
|
+
- `recurse:isArray` - Processing an array
|
|
1402
|
+
- `Fn::Map` - Inside an `Fn::Map` function
|
|
1403
|
+
- `Fn::Include` - Inside an `Fn::Include` function
|
|
1404
|
+
- `recurse:isPlainObject:end` - Final plain object processing
|
|
1405
|
+
- `handleIncludeBody:json` - JSON body being processed
|
|
1406
|
+
|
|
1407
|
+
When `doLog` is enabled, the console output will show the caller for each recursion:
|
|
1408
|
+
```javascript
|
|
1409
|
+
{
|
|
1410
|
+
base: {...},
|
|
1411
|
+
scope: {...},
|
|
1412
|
+
cft: {...},
|
|
1413
|
+
rootTemplate: {...},
|
|
1414
|
+
caller: "Fn::Map",
|
|
1415
|
+
doEnv: false,
|
|
1416
|
+
doEval: false,
|
|
1417
|
+
...
|
|
1418
|
+
}
|
|
1419
|
+
```
|
|
1420
|
+
|
|
1421
|
+
This makes it easy to trace execution flow through nested `Fn::Include`, `Fn::Map`, `Fn::RefNow`, and other functions.
|
|
1422
|
+
|
|
1423
|
+
#### Example Debug Output
|
|
1424
|
+
|
|
1425
|
+
```bash
|
|
1426
|
+
$ cfn-include examples/base.template --doLog | head -50
|
|
1427
|
+
{
|
|
1428
|
+
base: {
|
|
1429
|
+
protocol: 'file',
|
|
1430
|
+
host: '/Users/SOME_USER/code',
|
|
1431
|
+
path: '/examples/base.template'
|
|
1432
|
+
},
|
|
1433
|
+
scope: {},
|
|
1434
|
+
cft: { AWSTemplateFormatVersion: '2010-09-09', ... },
|
|
1435
|
+
rootTemplate: { AWSTemplateFormatVersion: '2010-09-09', ... },
|
|
1436
|
+
caller: 'recurse:isPlainObject:end',
|
|
1437
|
+
doEnv: false,
|
|
1438
|
+
doEval: false,
|
|
1439
|
+
inject: undefined,
|
|
1440
|
+
doLog: true
|
|
1441
|
+
}
|
|
1442
|
+
```
|
|
1443
|
+
|
|
1444
|
+
### Fn::RefNow Improvements
|
|
1445
|
+
|
|
1446
|
+
#### CLI Options for Reference Resolution
|
|
1447
|
+
|
|
1448
|
+
Two new CLI options control how unresolved `Fn::RefNow` references are handled:
|
|
1449
|
+
|
|
1450
|
+
- `--ref-now-ignore-missing`: Do not fail if a reference cannot be resolved. Instead, return the reference in CloudFormation's standard `Ref` syntax, allowing CloudFormation to resolve it at stack creation time.
|
|
1451
|
+
|
|
1452
|
+
- `--ref-now-ignores <names>`: Comma-separated list of specific reference names to ignore if not found. Useful for optional references.
|
|
1453
|
+
|
|
1454
|
+
**Example usage:**
|
|
1455
|
+
```bash
|
|
1456
|
+
# Ignore all unresolved references
|
|
1457
|
+
cfn-include template.yaml --ref-now-ignore-missing
|
|
1458
|
+
|
|
1459
|
+
# Ignore specific references
|
|
1460
|
+
cfn-include template.yaml --ref-now-ignores "OptionalParam,CustomRef"
|
|
1461
|
+
|
|
1462
|
+
# Combine both
|
|
1463
|
+
cfn-include template.yaml --ref-now-ignore-missing --ref-now-ignores "SpecificRef"
|
|
1464
|
+
```
|
|
1465
|
+
|
|
1466
|
+
#### rootTemplate Parameter
|
|
1467
|
+
|
|
1468
|
+
The `recurse` function now receives the complete `rootTemplate` for all recursion calls. This enables `Fn::RefNow` to resolve references to CloudFormation resources defined anywhere in the template, even when processing deeply nested includes or function results.
|
|
1469
|
+
|
|
1470
|
+
### Fn::SubNow and Fn::JoinNow
|
|
1471
|
+
|
|
1472
|
+
New intrinsic functions for immediate string substitution and joining:
|
|
1473
|
+
|
|
1474
|
+
- `Fn::SubNow` - Performs immediate string substitution similar to `Fn::Sub`, but evaluates at template processing time
|
|
1475
|
+
- `Fn::JoinNow` - Joins array elements into a string at template processing time
|
|
1476
|
+
|
|
1477
|
+
See the main documentation sections above for detailed usage.
|
|
1478
|
+
|
|
1479
|
+
### Template Processing Pipeline
|
|
1480
|
+
|
|
1481
|
+
The template processing follows this call chain for better debugging:
|
|
1482
|
+
|
|
1483
|
+
1. Entry point calls `recurse()` with `caller: undefined`
|
|
1484
|
+
2. Array elements call `recurse()` with `caller: 'recurse:isArray'`
|
|
1485
|
+
3. Each `Fn::*` function calls `recurse()` with `caller: 'Fn::FunctionName'`
|
|
1486
|
+
4. Final plain object recursion uses `caller: 'recurse:isPlainObject:end'`
|
|
1487
|
+
5. Include body processing uses `caller: 'handleIncludeBody:json'`
|
|
1488
|
+
|
|
1489
|
+
When combined with `--doLog`, this provides complete visibility into how `cfn-include` processes your templates.
|
|
1490
|
+
|
|
1373
1491
|
To compile the synopsis run the following command.
|
|
1374
1492
|
|
|
1375
1493
|
```
|
package/index.js
CHANGED
|
@@ -25,6 +25,7 @@ const replaceEnv = require('./lib/replaceEnv');
|
|
|
25
25
|
|
|
26
26
|
const { lowerCamelCase, upperCamelCase } = require('./lib/utils');
|
|
27
27
|
const { isOurExplicitFunction } = require('./lib/schema');
|
|
28
|
+
const { getAwsPseudoParameters, buildResourceArn } = require('./lib/internals');
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
31
|
* @param {object} options
|
|
@@ -37,6 +38,9 @@ const { isOurExplicitFunction } = require('./lib/schema');
|
|
|
37
38
|
* inject { KEY: Value } from where ${KEY}
|
|
38
39
|
* is substituted with Value
|
|
39
40
|
* @param {boolean} [options.doLog] log all arguments at the include recurse level
|
|
41
|
+
* @param {Array<string>} [options.refNowIgnores] array of logical resource IDs to ignore when resolving Fn::RefNow references
|
|
42
|
+
* @param {boolean} [options.refNowIgnoreMissing] if true, return Ref syntax for unresolvable references; if false, throw error
|
|
43
|
+
* @param {object} [options.rootTemplate] the root template object for resource lookups in Fn::RefNow
|
|
40
44
|
*
|
|
41
45
|
* Example: Load off off file system
|
|
42
46
|
* include({
|
|
@@ -61,7 +65,12 @@ module.exports = async function (options) {
|
|
|
61
65
|
: template;
|
|
62
66
|
// Resolve template if it's a promise to extract the root template for reference lookups
|
|
63
67
|
const resolvedTemplate = await Promise.resolve(template);
|
|
64
|
-
return recurse({
|
|
68
|
+
return recurse({
|
|
69
|
+
base, scope,
|
|
70
|
+
cft: resolvedTemplate,
|
|
71
|
+
rootTemplate: resolvedTemplate,
|
|
72
|
+
...options,
|
|
73
|
+
});
|
|
65
74
|
};
|
|
66
75
|
|
|
67
76
|
/**
|
|
@@ -91,185 +100,18 @@ module.exports = async function (options) {
|
|
|
91
100
|
* @param {Object} opts.inject object to inject { KEY: Value } from where ${KEY}
|
|
92
101
|
* is subtituted with Value
|
|
93
102
|
* @param {boolean} opts.doLog log all arguments at the include recurse level
|
|
103
|
+
* @param {Array<string>} [opts.refNowIgnores] array of logical resource IDs to ignore when resolving Fn::RefNow references
|
|
104
|
+
* @param {boolean} [opts.refNowIgnoreMissing] if true, return Ref syntax for unresolvable references; if false, throw error
|
|
105
|
+
* @param {object} [rootTemplate] the root template object for resource lookups in Fn::RefNow
|
|
106
|
+
* @param {String} [caller] internal use only for logging, to aid in who called recurse Fn::Include, Fn::RefNow, etc..
|
|
94
107
|
*/
|
|
95
|
-
function
|
|
96
|
-
return {
|
|
97
|
-
'AWS::AccountId': process.env.AWS_ACCOUNT_ID || process.env.AWS_ACCOUNT_NUM || '${AWS::AccountId}',
|
|
98
|
-
'AWS::Partition': process.env.AWS_PARTITION || 'aws',
|
|
99
|
-
'AWS::Region': process.env.AWS_REGION || '${AWS::Region}',
|
|
100
|
-
'AWS::StackId': process.env.AWS_STACK_ID || '${AWS::StackId}',
|
|
101
|
-
'AWS::StackName': process.env.AWS_STACK_NAME || '${AWS::StackName}',
|
|
102
|
-
'AWS::URLSuffix': process.env.AWS_URL_SUFFIX || 'amazonaws.com',
|
|
103
|
-
'AWS::NotificationARNs': process.env.AWS_NOTIFICATION_ARNS || '${AWS::NotificationARNs}',
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Build an ARN for a CloudFormation resource based on its Type and Properties
|
|
109
|
-
* Supports both ARN construction and property name resolution
|
|
110
|
-
* @param {string} resourceType - The CloudFormation resource type (e.g., 'AWS::IAM::ManagedPolicy')
|
|
111
|
-
* @param {object} properties - The resource properties
|
|
112
|
-
* @param {object} pseudoParams - AWS pseudo-parameters including AccountId, Region
|
|
113
|
-
* @param {object} options - Optional configuration
|
|
114
|
-
* @param {string} options.returnType - 'arn' (default) or 'name' to return the resource name/identifier
|
|
115
|
-
* @returns {string|null} - The ARN, name, or null if not applicable
|
|
116
|
-
*/
|
|
117
|
-
function buildResourceArn(resourceType, properties = {}, pseudoParams = {}, options = {}) {
|
|
118
|
-
const accountId = pseudoParams['AWS::AccountId'] || '${AWS::AccountId}';
|
|
119
|
-
const region = pseudoParams['AWS::Region'] || '${AWS::Region}';
|
|
120
|
-
const partition = pseudoParams['AWS::Partition'] || 'aws';
|
|
121
|
-
const returnType = options.returnType || 'arn';
|
|
122
|
-
|
|
123
|
-
// Handle AWS::IAM::ManagedPolicy
|
|
124
|
-
if (resourceType === 'AWS::IAM::ManagedPolicy') {
|
|
125
|
-
const { ManagedPolicyName, Path } = properties;
|
|
126
|
-
if (ManagedPolicyName) {
|
|
127
|
-
if (returnType === 'name') {
|
|
128
|
-
return ManagedPolicyName;
|
|
129
|
-
}
|
|
130
|
-
const path = Path || '/';
|
|
131
|
-
return `arn:${partition}:iam::${accountId}:policy${path}${ManagedPolicyName}`;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Handle AWS::IAM::Role
|
|
136
|
-
if (resourceType === 'AWS::IAM::Role') {
|
|
137
|
-
const { RoleName, Path } = properties;
|
|
138
|
-
if (RoleName) {
|
|
139
|
-
if (returnType === 'name') {
|
|
140
|
-
return RoleName;
|
|
141
|
-
}
|
|
142
|
-
const path = Path || '/';
|
|
143
|
-
return `arn:${partition}:iam::${accountId}:role${path}${RoleName}`;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Handle AWS::S3::Bucket
|
|
148
|
-
if (resourceType === 'AWS::S3::Bucket') {
|
|
149
|
-
const { BucketName } = properties;
|
|
150
|
-
if (BucketName) {
|
|
151
|
-
if (returnType === 'name') {
|
|
152
|
-
return BucketName;
|
|
153
|
-
}
|
|
154
|
-
return `arn:${partition}:s3:::${BucketName}`;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Handle AWS::Lambda::Function
|
|
159
|
-
if (resourceType === 'AWS::Lambda::Function') {
|
|
160
|
-
const { FunctionName } = properties;
|
|
161
|
-
if (FunctionName) {
|
|
162
|
-
if (returnType === 'name') {
|
|
163
|
-
return FunctionName;
|
|
164
|
-
}
|
|
165
|
-
return `arn:${partition}:lambda:${region}:${accountId}:function:${FunctionName}`;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Handle AWS::SQS::Queue
|
|
170
|
-
if (resourceType === 'AWS::SQS::Queue') {
|
|
171
|
-
const { QueueName } = properties;
|
|
172
|
-
if (QueueName) {
|
|
173
|
-
if (returnType === 'name') {
|
|
174
|
-
return QueueName;
|
|
175
|
-
}
|
|
176
|
-
return `arn:${partition}:sqs:${region}:${accountId}:${QueueName}`;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Handle AWS::SNS::Topic
|
|
181
|
-
if (resourceType === 'AWS::SNS::Topic') {
|
|
182
|
-
const { TopicName } = properties;
|
|
183
|
-
if (TopicName) {
|
|
184
|
-
if (returnType === 'name') {
|
|
185
|
-
return TopicName;
|
|
186
|
-
}
|
|
187
|
-
return `arn:${partition}:sns:${region}:${accountId}:${TopicName}`;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// Handle AWS::DynamoDB::Table
|
|
192
|
-
if (resourceType === 'AWS::DynamoDB::Table') {
|
|
193
|
-
const { TableName } = properties;
|
|
194
|
-
if (TableName) {
|
|
195
|
-
if (returnType === 'name') {
|
|
196
|
-
return TableName;
|
|
197
|
-
}
|
|
198
|
-
return `arn:${partition}:dynamodb:${region}:${accountId}:table/${TableName}`;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Handle AWS::RDS::DBInstance
|
|
203
|
-
if (resourceType === 'AWS::RDS::DBInstance') {
|
|
204
|
-
const { DBInstanceIdentifier } = properties;
|
|
205
|
-
if (DBInstanceIdentifier) {
|
|
206
|
-
if (returnType === 'name') {
|
|
207
|
-
return DBInstanceIdentifier;
|
|
208
|
-
}
|
|
209
|
-
return `arn:${partition}:rds:${region}:${accountId}:db:${DBInstanceIdentifier}`;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Handle AWS::EC2::SecurityGroup
|
|
214
|
-
if (resourceType === 'AWS::EC2::SecurityGroup') {
|
|
215
|
-
const { GroupName } = properties;
|
|
216
|
-
if (GroupName) {
|
|
217
|
-
if (returnType === 'name') {
|
|
218
|
-
return GroupName;
|
|
219
|
-
}
|
|
220
|
-
// Security groups need to be referenced by ID in most cases, not ARN
|
|
221
|
-
// This returns the name for reference purposes
|
|
222
|
-
return GroupName;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Handle AWS::IAM::InstanceProfile
|
|
227
|
-
if (resourceType === 'AWS::IAM::InstanceProfile') {
|
|
228
|
-
const { InstanceProfileName, Path } = properties;
|
|
229
|
-
if (InstanceProfileName) {
|
|
230
|
-
if (returnType === 'name') {
|
|
231
|
-
return InstanceProfileName;
|
|
232
|
-
}
|
|
233
|
-
const path = Path || '/';
|
|
234
|
-
return `arn:${partition}:iam::${accountId}:instance-profile${path}${InstanceProfileName}`;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Handle AWS::KMS::Key
|
|
239
|
-
if (resourceType === 'AWS::KMS::Key') {
|
|
240
|
-
// KMS keys are referenced by KeyId or KeyArn in properties
|
|
241
|
-
const { KeyId } = properties;
|
|
242
|
-
if (KeyId) {
|
|
243
|
-
if (returnType === 'name') {
|
|
244
|
-
return KeyId;
|
|
245
|
-
}
|
|
246
|
-
return `arn:${partition}:kms:${region}:${accountId}:key/${KeyId}`;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// Handle AWS::SecretsManager::Secret
|
|
251
|
-
if (resourceType === 'AWS::SecretsManager::Secret') {
|
|
252
|
-
const { Name } = properties;
|
|
253
|
-
if (Name) {
|
|
254
|
-
if (returnType === 'name') {
|
|
255
|
-
return Name;
|
|
256
|
-
}
|
|
257
|
-
return `arn:${partition}:secretsmanager:${region}:${accountId}:secret:${Name}`;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// Add more resource types as needed
|
|
262
|
-
return null;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
async function recurse({ base, scope, cft, ...opts }) {
|
|
108
|
+
async function recurse({ base, scope, cft, rootTemplate, caller, ...opts }) {
|
|
266
109
|
if (opts.doLog) {
|
|
267
|
-
|
|
268
|
-
console.log({ base, scope, cft, ...opts });
|
|
110
|
+
console.log({ base, scope, cft, rootTemplate, caller, ...opts });
|
|
269
111
|
}
|
|
270
112
|
scope = _.clone(scope);
|
|
271
113
|
if (_.isArray(cft)) {
|
|
272
|
-
return Promise.all(cft.map((o) => recurse({ base, scope, cft: o, ...opts })));
|
|
114
|
+
return Promise.all(cft.map((o) => recurse({ base, scope, cft: o, rootTemplate, caller: 'recurse:isArray', ...opts })));
|
|
273
115
|
}
|
|
274
116
|
if (_.isPlainObject(cft)) {
|
|
275
117
|
if (cft['Fn::Map']) {
|
|
@@ -294,26 +136,26 @@ async function recurse({ base, scope, cft, ...opts }) {
|
|
|
294
136
|
if (args.length === 2) {
|
|
295
137
|
placeholder = '_';
|
|
296
138
|
}
|
|
297
|
-
return PromiseExt.mapX(recurse({ base, scope, cft: list, ...opts }), (replace, key) => {
|
|
139
|
+
return PromiseExt.mapX(recurse({ base, scope, cft: list, rootTemplate, caller: 'Fn::Map', ...opts }), (replace, key) => {
|
|
298
140
|
scope = _.clone(scope);
|
|
299
141
|
scope[placeholder] = replace;
|
|
300
142
|
if (hasindex) {
|
|
301
143
|
scope[idx] = key;
|
|
302
144
|
}
|
|
303
145
|
const replaced = findAndReplace(scope, _.cloneDeep(body));
|
|
304
|
-
return recurse({ base, scope, cft: replaced, ...opts });
|
|
146
|
+
return recurse({ base, scope, cft: replaced, rootTemplate, caller: 'Fn::Map', ...opts });
|
|
305
147
|
}).then((_cft) => {
|
|
306
148
|
if (hassize) {
|
|
307
149
|
_cft = findAndReplace({ [sz]: _cft.length }, _cft);
|
|
308
150
|
}
|
|
309
|
-
return recurse({ base, scope, cft: _cft, ...opts });
|
|
151
|
+
return recurse({ base, scope, cft: _cft, rootTemplate, caller: 'Fn::Map', ...opts });
|
|
310
152
|
});
|
|
311
153
|
}
|
|
312
154
|
if (cft['Fn::Length']) {
|
|
313
155
|
if (Array.isArray(cft['Fn::Length'])) {
|
|
314
156
|
return cft['Fn::Length'].length;
|
|
315
157
|
}
|
|
316
|
-
return recurse({ base, scope, cft: cft['Fn::Length'], ...opts }).then((x) => {
|
|
158
|
+
return recurse({ base, scope, cft: cft['Fn::Length'], rootTemplate, caller: 'Fn::Length', ...opts }).then((x) => {
|
|
317
159
|
if (Array.isArray(x)) {
|
|
318
160
|
return x.length;
|
|
319
161
|
}
|
|
@@ -329,49 +171,49 @@ async function recurse({ base, scope, cft, ...opts }) {
|
|
|
329
171
|
return cft;
|
|
330
172
|
})
|
|
331
173
|
.then((_cft) => findAndReplace(scope, _cft))
|
|
332
|
-
.then((t) => recurse({ base, scope, cft: t, ...opts }));
|
|
174
|
+
.then((t) => recurse({ base, scope, cft: t, rootTemplate, caller: 'Fn::Include', ...opts }));
|
|
333
175
|
}
|
|
334
176
|
if (cft['Fn::Flatten']) {
|
|
335
|
-
return recurse({ base, scope, cft: cft['Fn::Flatten'], ...opts }).then(function (json) {
|
|
177
|
+
return recurse({ base, scope, cft: cft['Fn::Flatten'], rootTemplate, caller: 'Fn::Flatten', ...opts }).then(function (json) {
|
|
336
178
|
return _.flatten(json);
|
|
337
179
|
});
|
|
338
180
|
}
|
|
339
181
|
if (cft['Fn::FlattenDeep']) {
|
|
340
|
-
return recurse({ base, scope, cft: cft['Fn::FlattenDeep'], ...opts }).then(
|
|
182
|
+
return recurse({ base, scope, cft: cft['Fn::FlattenDeep'], rootTemplate, caller: 'Fn::FlattenDeep', ...opts }).then(
|
|
341
183
|
function (json) {
|
|
342
184
|
return _.flattenDeep(json);
|
|
343
185
|
},
|
|
344
186
|
);
|
|
345
187
|
}
|
|
346
188
|
if (cft['Fn::Uniq']) {
|
|
347
|
-
return recurse({ base, scope, cft: cft['Fn::Uniq'], ...opts }).then(function (json) {
|
|
189
|
+
return recurse({ base, scope, cft: cft['Fn::Uniq'], rootTemplate, caller: 'Fn::Uniq', ...opts }).then(function (json) {
|
|
348
190
|
return _.uniq(json);
|
|
349
191
|
});
|
|
350
192
|
}
|
|
351
193
|
if (cft['Fn::Compact']) {
|
|
352
|
-
return recurse({ base, scope, cft: cft['Fn::Compact'], ...opts }).then(function (json) {
|
|
194
|
+
return recurse({ base, scope, cft: cft['Fn::Compact'], rootTemplate, caller: 'Fn::Compact', ...opts }).then(function (json) {
|
|
353
195
|
return _.compact(json);
|
|
354
196
|
});
|
|
355
197
|
}
|
|
356
198
|
if (cft['Fn::Concat']) {
|
|
357
|
-
return recurse({ base, scope, cft: cft['Fn::Concat'], ...opts }).then(function (json) {
|
|
199
|
+
return recurse({ base, scope, cft: cft['Fn::Concat'], rootTemplate, caller: 'Fn::Concat', ...opts }).then(function (json) {
|
|
358
200
|
return _.concat(...json);
|
|
359
201
|
});
|
|
360
202
|
}
|
|
361
203
|
if (cft['Fn::Sort']) {
|
|
362
|
-
return recurse({ base, scope, cft: cft['Fn::Sort'], ...opts }).then(function (array) {
|
|
204
|
+
return recurse({ base, scope, cft: cft['Fn::Sort'], rootTemplate, caller: 'Fn::Sort', ...opts }).then(function (array) {
|
|
363
205
|
return array.sort();
|
|
364
206
|
});
|
|
365
207
|
}
|
|
366
208
|
if (cft['Fn::SortedUniq']) {
|
|
367
|
-
return recurse({ base, scope, cft: cft['Fn::SortedUniq'], ...opts }).then(
|
|
209
|
+
return recurse({ base, scope, cft: cft['Fn::SortedUniq'], rootTemplate, caller: 'Fn::SortedUniq', ...opts }).then(
|
|
368
210
|
function (array) {
|
|
369
211
|
return _.sortedUniq(array.sort());
|
|
370
212
|
},
|
|
371
213
|
);
|
|
372
214
|
}
|
|
373
215
|
if (cft['Fn::SortBy']) {
|
|
374
|
-
return recurse({ base, scope, cft: cft['Fn::SortBy'], ...opts }).then(function ({
|
|
216
|
+
return recurse({ base, scope, cft: cft['Fn::SortBy'], rootTemplate, caller: 'Fn::SortBy', ...opts }).then(function ({
|
|
375
217
|
list,
|
|
376
218
|
iteratees,
|
|
377
219
|
}) {
|
|
@@ -379,7 +221,7 @@ async function recurse({ base, scope, cft, ...opts }) {
|
|
|
379
221
|
});
|
|
380
222
|
}
|
|
381
223
|
if (cft['Fn::SortObject']) {
|
|
382
|
-
return recurse({ base, scope, cft: cft['Fn::SortObject'], ...opts }).then(function ({
|
|
224
|
+
return recurse({ base, scope, cft: cft['Fn::SortObject'], rootTemplate, caller: 'Fn::SortObject', ...opts }).then(function ({
|
|
383
225
|
|
|
384
226
|
object,
|
|
385
227
|
options,
|
|
@@ -389,19 +231,19 @@ async function recurse({ base, scope, cft, ...opts }) {
|
|
|
389
231
|
});
|
|
390
232
|
}
|
|
391
233
|
if (cft['Fn::Without']) {
|
|
392
|
-
return recurse({ base, scope, cft: cft['Fn::Without'], ...opts }).then(function (json) {
|
|
234
|
+
return recurse({ base, scope, cft: cft['Fn::Without'], rootTemplate, caller: 'Fn::Without', ...opts }).then(function (json) {
|
|
393
235
|
json = Array.isArray(json) ? { list: json[0], withouts: json[1] } : json;
|
|
394
236
|
return _.without(json.list, ...json.withouts);
|
|
395
237
|
});
|
|
396
238
|
}
|
|
397
239
|
if (cft['Fn::Omit']) {
|
|
398
|
-
return recurse({ base, scope, cft: cft['Fn::Omit'], ...opts }).then(function (json) {
|
|
240
|
+
return recurse({ base, scope, cft: cft['Fn::Omit'], rootTemplate, caller: 'Fn::Omit', ...opts }).then(function (json) {
|
|
399
241
|
json = Array.isArray(json) ? { object: json[0], omits: json[1] } : json;
|
|
400
242
|
return _.omit(json.object, json.omits);
|
|
401
243
|
});
|
|
402
244
|
}
|
|
403
245
|
if (cft['Fn::OmitEmpty']) {
|
|
404
|
-
return recurse({ base, scope, cft: cft['Fn::OmitEmpty'], ...opts }).then(
|
|
246
|
+
return recurse({ base, scope, cft: cft['Fn::OmitEmpty'], rootTemplate, caller: 'Fn::OmitEmpty', ...opts }).then(
|
|
405
247
|
function (json) {
|
|
406
248
|
// omit falsy values except false, and 0
|
|
407
249
|
return _.omitBy(json, (v) => !v && v !== false && v !== 0);
|
|
@@ -412,7 +254,7 @@ async function recurse({ base, scope, cft, ...opts }) {
|
|
|
412
254
|
if (!opts.doEval) {
|
|
413
255
|
return Promise.reject(new Error('Fn::Eval is not allowed doEval is falsy'));
|
|
414
256
|
}
|
|
415
|
-
return recurse({ base, scope, cft: cft['Fn::Eval'], ...opts }).then(function (json) {
|
|
257
|
+
return recurse({ base, scope, cft: cft['Fn::Eval'], rootTemplate, caller: 'Fn::Eval', ...opts }).then(function (json) {
|
|
416
258
|
// **WARNING** you have now enabled god mode
|
|
417
259
|
|
|
418
260
|
let { state, script, inject, doLog } = json;
|
|
@@ -426,7 +268,7 @@ async function recurse({ base, scope, cft, ...opts }) {
|
|
|
426
268
|
});
|
|
427
269
|
}
|
|
428
270
|
if (cft['Fn::Filenames']) {
|
|
429
|
-
return recurse({ base, scope, cft: cft['Fn::Filenames'], ...opts }).then(
|
|
271
|
+
return recurse({ base, scope, cft: cft['Fn::Filenames'], rootTemplate, caller: 'Fn::Filenames', ...opts }).then(
|
|
430
272
|
function (json) {
|
|
431
273
|
json = _.isPlainObject(json) ? { ...json } : { location: json };
|
|
432
274
|
if (json.doLog) {
|
|
@@ -453,14 +295,14 @@ async function recurse({ base, scope, cft, ...opts }) {
|
|
|
453
295
|
);
|
|
454
296
|
}
|
|
455
297
|
if (cft['Fn::Merge']) {
|
|
456
|
-
return recurse({ base, scope, cft: cft['Fn::Merge'], ...opts }).then(function (json) {
|
|
298
|
+
return recurse({ base, scope, cft: cft['Fn::Merge'], rootTemplate, caller: 'Fn::Merge', ...opts }).then(function (json) {
|
|
457
299
|
delete cft['Fn::Merge'];
|
|
458
300
|
|
|
459
|
-
return recurse({ base, scope, cft: _.defaults(cft, _.merge.apply(_, json)), ...opts });
|
|
301
|
+
return recurse({ base, scope, cft: _.defaults(cft, _.merge.apply(_, json)), rootTemplate, caller: 'Fn::Merge', ...opts });
|
|
460
302
|
});
|
|
461
303
|
}
|
|
462
304
|
if (cft['Fn::DeepMerge']) {
|
|
463
|
-
return recurse({ base, scope, cft: cft['Fn::DeepMerge'], ...opts }).then(
|
|
305
|
+
return recurse({ base, scope, cft: cft['Fn::DeepMerge'], rootTemplate, caller: 'Fn::DeepMerge', ...opts }).then(
|
|
464
306
|
function (json) {
|
|
465
307
|
delete cft['Fn::DeepMerge'];
|
|
466
308
|
let mergedObj = {};
|
|
@@ -469,29 +311,29 @@ async function recurse({ base, scope, cft, ...opts }) {
|
|
|
469
311
|
mergedObj = deepMerge(mergedObj, j);
|
|
470
312
|
});
|
|
471
313
|
}
|
|
472
|
-
return recurse({ base, scope, cft: _.defaults(cft, mergedObj), ...opts });
|
|
314
|
+
return recurse({ base, scope, cft: _.defaults(cft, mergedObj), rootTemplate, caller: 'Fn::DeepMerge', ...opts });
|
|
473
315
|
},
|
|
474
316
|
);
|
|
475
317
|
}
|
|
476
318
|
if (cft['Fn::ObjectKeys']) {
|
|
477
|
-
return recurse({ base, scope, cft: cft['Fn::ObjectKeys'], ...opts }).then((json) =>
|
|
319
|
+
return recurse({ base, scope, cft: cft['Fn::ObjectKeys'], rootTemplate, caller: 'Fn::ObjectKeys', ...opts }).then((json) =>
|
|
478
320
|
Object.keys(json),
|
|
479
321
|
);
|
|
480
322
|
}
|
|
481
323
|
if (cft['Fn::ObjectValues']) {
|
|
482
|
-
return recurse({ base, scope, cft: cft['Fn::ObjectValues'], ...opts }).then((json) =>
|
|
324
|
+
return recurse({ base, scope, cft: cft['Fn::ObjectValues'], rootTemplate, caller: 'Fn::ObjectValues', ...opts }).then((json) =>
|
|
483
325
|
Object.values(json),
|
|
484
326
|
);
|
|
485
327
|
}
|
|
486
328
|
if (cft['Fn::Stringify']) {
|
|
487
|
-
return recurse({ base, scope, cft: cft['Fn::Stringify'], ...opts }).then(
|
|
329
|
+
return recurse({ base, scope, cft: cft['Fn::Stringify'], rootTemplate, caller: 'Fn::Stringify', ...opts }).then(
|
|
488
330
|
function (json) {
|
|
489
331
|
return JSON.stringify(json);
|
|
490
332
|
},
|
|
491
333
|
);
|
|
492
334
|
}
|
|
493
335
|
if (cft['Fn::StringSplit']) {
|
|
494
|
-
return recurse({ base, scope, cft: cft['Fn::StringSplit'], ...opts }).then(
|
|
336
|
+
return recurse({ base, scope, cft: cft['Fn::StringSplit'], rootTemplate, caller: 'Fn::StringSplit', ...opts }).then(
|
|
495
337
|
({ string, separator, doLog }) => {
|
|
496
338
|
if (!string) {
|
|
497
339
|
string = '';
|
|
@@ -526,7 +368,7 @@ async function recurse({ base, scope, cft, ...opts }) {
|
|
|
526
368
|
}
|
|
527
369
|
|
|
528
370
|
if (cft['Fn::Outputs']) {
|
|
529
|
-
const outputs = await recurse({ base, scope, cft: cft['Fn::Outputs'], ...opts });
|
|
371
|
+
const outputs = await recurse({ base, scope, cft: cft['Fn::Outputs'], caller: 'Fn::Outputs', ...opts });
|
|
530
372
|
const result = {};
|
|
531
373
|
|
|
532
374
|
for (const output in outputs) {
|
|
@@ -551,7 +393,7 @@ async function recurse({ base, scope, cft, ...opts }) {
|
|
|
551
393
|
}
|
|
552
394
|
|
|
553
395
|
if (cft['Fn::Sequence']) {
|
|
554
|
-
const outputs = await recurse({ base, scope, cft: cft['Fn::Sequence'], ...opts });
|
|
396
|
+
const outputs = await recurse({ base, scope, cft: cft['Fn::Sequence'], caller: 'Fn::Sequence', ...opts });
|
|
555
397
|
|
|
556
398
|
let [start, stop, step = 1] = outputs;
|
|
557
399
|
const isString = typeof start === 'string';
|
|
@@ -570,7 +412,7 @@ async function recurse({ base, scope, cft, ...opts }) {
|
|
|
570
412
|
if (!opts.doEval) {
|
|
571
413
|
return Promise.reject(new Error('Fn::IfEval is not allowed doEval is falsy'));
|
|
572
414
|
}
|
|
573
|
-
return recurse({ base, scope, cft: cft['Fn::IfEval'], ...opts }).then(function (json) {
|
|
415
|
+
return recurse({ base, scope, cft: cft['Fn::IfEval'], rootTemplate, caller: 'Fn::IfEval', ...opts }).then(function (json) {
|
|
574
416
|
|
|
575
417
|
let { truthy, falsy, evalCond, inject, doLog } = json;
|
|
576
418
|
if (!evalCond) {
|
|
@@ -599,13 +441,13 @@ async function recurse({ base, scope, cft, ...opts }) {
|
|
|
599
441
|
}
|
|
600
442
|
|
|
601
443
|
if (condResult) {
|
|
602
|
-
return recurse({ base, scope, cft: truthy, ...opts });
|
|
444
|
+
return recurse({ base, scope, cft: truthy, rootTemplate, caller: 'Fn::IfEval', ...opts });
|
|
603
445
|
}
|
|
604
|
-
return recurse({ base, scope, cft: falsy, ...opts });
|
|
446
|
+
return recurse({ base, scope, cft: falsy, rootTemplate, caller: 'Fn::IfEval', ...opts });
|
|
605
447
|
});
|
|
606
448
|
}
|
|
607
449
|
if (cft['Fn::JoinNow']) {
|
|
608
|
-
return recurse({ base, scope, cft: cft['Fn::JoinNow'], ...opts }).then((array) => {
|
|
450
|
+
return recurse({ base, scope, cft: cft['Fn::JoinNow'], rootTemplate, caller: 'Fn::JoinNow', ...opts }).then((array) => {
|
|
609
451
|
// keeps with same format as Fn::Join ~ more complex
|
|
610
452
|
// vs let [delimitter, ...toJoinArray] = array;
|
|
611
453
|
let [delimitter, toJoinArray] = array;
|
|
@@ -614,7 +456,7 @@ async function recurse({ base, scope, cft, ...opts }) {
|
|
|
614
456
|
});
|
|
615
457
|
}
|
|
616
458
|
if (cft['Fn::SubNow']) {
|
|
617
|
-
return recurse({ base, scope, cft: cft['Fn::SubNow'], ...opts }).then((input) => {
|
|
459
|
+
return recurse({ base, scope, cft: cft['Fn::SubNow'], rootTemplate, caller: 'Fn::SubNow', ...opts }).then((input) => {
|
|
618
460
|
let template = input;
|
|
619
461
|
let variables = {};
|
|
620
462
|
|
|
@@ -641,9 +483,13 @@ async function recurse({ base, scope, cft, ...opts }) {
|
|
|
641
483
|
});
|
|
642
484
|
}
|
|
643
485
|
if (cft['Fn::RefNow']) {
|
|
486
|
+
// console.log("outter");
|
|
487
|
+
// console.log(opts);
|
|
644
488
|
// Handle both simple string and object format for Fn::RefNow
|
|
645
489
|
// e.g., Fn::RefNow: "MyRole" or Fn::RefNow: { Ref: "MyRole" }
|
|
646
|
-
return recurse({ base, scope, cft: cft['Fn::RefNow'], ...opts }).then((refInput) => {
|
|
490
|
+
return recurse({ base, scope, cft: cft['Fn::RefNow'], rootTemplate, caller: 'Fn::RefNow', ...opts }).then((refInput) => {
|
|
491
|
+
// console.log("inner");
|
|
492
|
+
// console.log(opts);
|
|
647
493
|
let refName = refInput;
|
|
648
494
|
let refOptions = {};
|
|
649
495
|
|
|
@@ -672,13 +518,13 @@ async function recurse({ base, scope, cft, ...opts }) {
|
|
|
672
518
|
}
|
|
673
519
|
|
|
674
520
|
// Check if this is a LogicalResourceId in the Resources section
|
|
675
|
-
if (
|
|
676
|
-
const resources =
|
|
521
|
+
if (rootTemplate && rootTemplate.Resources) {
|
|
522
|
+
const resources = rootTemplate.Resources;
|
|
677
523
|
if (refName in resources) {
|
|
678
524
|
const resource = resources[refName];
|
|
679
525
|
const resourceType = resource.Type;
|
|
680
526
|
const properties = resource.Properties || {};
|
|
681
|
-
|
|
527
|
+
|
|
682
528
|
// Determine return type based on key name
|
|
683
529
|
// If the key ends with "Name" (e.g., RoleName, BucketName), return name/identifier
|
|
684
530
|
// Otherwise return ARN (default)
|
|
@@ -686,10 +532,10 @@ async function recurse({ base, scope, cft, ...opts }) {
|
|
|
686
532
|
if (opts.key && opts.key.endsWith('Name')) {
|
|
687
533
|
returnType = 'name';
|
|
688
534
|
}
|
|
689
|
-
|
|
535
|
+
|
|
690
536
|
// Try to build an ARN or name for this resource
|
|
691
537
|
// Merge ref options with global options (ref options take precedence)
|
|
692
|
-
const resourceOptions = {
|
|
538
|
+
const resourceOptions = {
|
|
693
539
|
returnType,
|
|
694
540
|
...opts.refNowReturnType ? { returnType: opts.refNowReturnType } : {},
|
|
695
541
|
...refOptions,
|
|
@@ -711,7 +557,7 @@ async function recurse({ base, scope, cft, ...opts }) {
|
|
|
711
557
|
});
|
|
712
558
|
}
|
|
713
559
|
if (cft['Fn::ApplyTags']) {
|
|
714
|
-
return recurse({ base, scope, cft: cft['Fn::ApplyTags'], ...opts }).then((json) => {
|
|
560
|
+
return recurse({ base, scope, cft: cft['Fn::ApplyTags'], rootTemplate, caller: 'Fn::ApplyTags', ...opts }).then((json) => {
|
|
715
561
|
let { tags, Tags, resources } = json;
|
|
716
562
|
tags = tags || Tags; // allow for both caseing
|
|
717
563
|
const promises = [];
|
|
@@ -737,7 +583,7 @@ async function recurse({ base, scope, cft, ...opts }) {
|
|
|
737
583
|
}
|
|
738
584
|
|
|
739
585
|
return Promise.props(
|
|
740
|
-
_.mapValues(cft, (template, key) => recurse({ base, scope, cft: template, key, ...opts })),
|
|
586
|
+
_.mapValues(cft, (template, key) => recurse({ base, scope, cft: template, key, rootTemplate, caller: 'recurse:isPlainObject:end', ...opts })),
|
|
741
587
|
);
|
|
742
588
|
}
|
|
743
589
|
|
|
@@ -831,6 +677,21 @@ function fnIncludeOpts(cft, opts) {
|
|
|
831
677
|
return cft;
|
|
832
678
|
}
|
|
833
679
|
|
|
680
|
+
/**
|
|
681
|
+
* Process includes for template documents
|
|
682
|
+
* @param {object} base file options (protocol, host, path, relative, raw)
|
|
683
|
+
* @param {object} scope variable scope for Fn::Map substitutions
|
|
684
|
+
* @param {Document|String|Array} cft include specification (location, query, parser, type, etc.)
|
|
685
|
+
* @param {Object} opts processing options
|
|
686
|
+
* @param {boolean} [opts.doEnv] inject environment from process.env
|
|
687
|
+
* @param {boolean} [opts.doEval] allow Fn::Eval to be used
|
|
688
|
+
* @param {Object} [opts.inject] object to inject { KEY: Value }
|
|
689
|
+
* @param {boolean} [opts.doLog] log all arguments at the include recurse level
|
|
690
|
+
* @param {Array<string>} [opts.refNowIgnores] array of logical resource IDs to ignore when resolving Fn::RefNow references
|
|
691
|
+
* @param {boolean} [opts.refNowIgnoreMissing] if true, return Ref syntax for unresolvable references; if false, throw error
|
|
692
|
+
* @param {object} [opts.rootTemplate] the root template object for resource lookups in Fn::RefNow
|
|
693
|
+
* @returns {Promise} resolved include content
|
|
694
|
+
*/
|
|
834
695
|
async function fnInclude({ base, scope, cft, ...opts }) {
|
|
835
696
|
let procTemplate = async (template, inject = cft.inject, doEnv = opts.doEnv) =>
|
|
836
697
|
replaceEnv(template, inject, doEnv);
|
|
@@ -852,7 +713,6 @@ async function fnInclude({ base, scope, cft, ...opts }) {
|
|
|
852
713
|
cft = fnIncludeOpts(cft, opts);
|
|
853
714
|
|
|
854
715
|
if (cft.doLog) {
|
|
855
|
-
|
|
856
716
|
console.log({ base, scope, args: cft, ...opts });
|
|
857
717
|
}
|
|
858
718
|
// console.log(args)
|
|
@@ -872,14 +732,14 @@ async function fnInclude({ base, scope, cft, ...opts }) {
|
|
|
872
732
|
if (isGlob(cft, absolute)) {
|
|
873
733
|
const paths = globSync(absolute).sort();
|
|
874
734
|
const template = yaml.load(paths.map((_p) => `- Fn::Include: file://${_p}`).join('\n'));
|
|
875
|
-
return recurse({ base, scope, cft: template, ...opts });
|
|
735
|
+
return recurse({ base, scope, cft: template, rootTemplate: template, ...opts });
|
|
876
736
|
}
|
|
877
737
|
body = readFile(absolute).then(String).then(procTemplate);
|
|
878
738
|
absolute = `${location.protocol}://${absolute}`;
|
|
879
739
|
} else if (location.protocol === 's3') {
|
|
880
740
|
const basedir = pathParse(base.path).dir;
|
|
881
741
|
const bucket = location.relative ? base.host : location.host;
|
|
882
|
-
|
|
742
|
+
|
|
883
743
|
let key = location.relative ? url.resolve(`${basedir}/`, location.raw) : location.path;
|
|
884
744
|
key = key.replace(/^\//, '');
|
|
885
745
|
absolute = `${location.protocol}://${[bucket, key].join('/')}`;
|
|
@@ -894,11 +754,11 @@ async function fnInclude({ base, scope, cft, ...opts }) {
|
|
|
894
754
|
.then(procTemplate);
|
|
895
755
|
} else if (location.protocol && location.protocol.match(/^https?$/)) {
|
|
896
756
|
const basepath = `${pathParse(base.path).dir}/`;
|
|
897
|
-
|
|
757
|
+
|
|
898
758
|
absolute = location.relative
|
|
899
759
|
? url.resolve(`${location.protocol}://${base.host}${basepath}`, location.raw)
|
|
900
760
|
: location.raw;
|
|
901
|
-
|
|
761
|
+
|
|
902
762
|
body = request(absolute).then(procTemplate);
|
|
903
763
|
}
|
|
904
764
|
return handleIncludeBody({ scope, args: cft, body, absolute });
|
|
@@ -908,6 +768,27 @@ function isGlob(args, str) {
|
|
|
908
768
|
return args.isGlob || /.*\*/.test(str);
|
|
909
769
|
}
|
|
910
770
|
|
|
771
|
+
/**
|
|
772
|
+
* Process the resolved include body content
|
|
773
|
+
* @param {object} config configuration object
|
|
774
|
+
* @param {object} config.scope variable scope for substitutions
|
|
775
|
+
* @param {Object} config.args include arguments (type, query, parser, inject, doEnv, doEval, doLog, refNowIgnores, refNowIgnoreMissing, rootTemplate)
|
|
776
|
+
* @param {Promise} config.body promise resolving to the file content
|
|
777
|
+
* @param {string} config.absolute absolute path/URL to the included resource
|
|
778
|
+
* @param {string} [config.args.type='json'] type of content to process (json, string, literal)
|
|
779
|
+
* @param {Object|string|Array} [config.args.query] query to apply to the processed template
|
|
780
|
+
* @param {string} [config.args.parser='lodash'] parser to use for querying (lodash, jsonpath)
|
|
781
|
+
* @param {Object} [config.args.context] context object for literal interpolation
|
|
782
|
+
* @param {Object} [config.args.inject] object to inject { KEY: Value }
|
|
783
|
+
* @param {boolean} [config.args.doEnv] inject environment from process.env
|
|
784
|
+
* @param {boolean} [config.args.doEval] allow Fn::Eval to be used
|
|
785
|
+
* @param {boolean} [config.args.doLog] log all arguments at the include recurse level
|
|
786
|
+
* @param {Array<string>} [config.args.refNowIgnores] array of logical resource IDs to ignore when resolving Fn::RefNow references
|
|
787
|
+
* @param {boolean} [config.args.refNowIgnoreMissing] if true, return Ref syntax for unresolvable references; if false, throw error
|
|
788
|
+
*
|
|
789
|
+
* rootTeamplate is not an argument here cause it's loaded via arg.type
|
|
790
|
+
* @returns {Promise} processed template content
|
|
791
|
+
*/
|
|
911
792
|
async function handleIncludeBody({ scope, args, body, absolute }) {
|
|
912
793
|
const procTemplate = (temp) => replaceEnv(temp, args.inject, args.doEnv);
|
|
913
794
|
try {
|
|
@@ -915,18 +796,22 @@ async function handleIncludeBody({ scope, args, body, absolute }) {
|
|
|
915
796
|
case 'json': {
|
|
916
797
|
let b = await body;
|
|
917
798
|
b = procTemplate(b);
|
|
918
|
-
const
|
|
919
|
-
|
|
799
|
+
const rootTemplate = yaml.load(b);
|
|
800
|
+
const caller = 'handleIncludeBody:json';
|
|
920
801
|
// recurse all the way down and work your way out
|
|
921
802
|
const loopTemplate = (temp) => {
|
|
922
803
|
return recurse({
|
|
923
804
|
base: parseLocation(absolute),
|
|
924
805
|
scope,
|
|
925
806
|
cft: temp,
|
|
807
|
+
caller,
|
|
808
|
+
rootTemplate,
|
|
926
809
|
doEnv: args.doEnv,
|
|
927
810
|
doEval: args.doEval,
|
|
928
811
|
doLog: args.doLog,
|
|
929
812
|
inject: args.inject,
|
|
813
|
+
refNowIgnores: args.refNowIgnores,
|
|
814
|
+
refNowIgnoreMissing: args.refNowIgnoreMissing, // note we can't use ...args here because of circular ref
|
|
930
815
|
}).then((_temp) => {
|
|
931
816
|
if (!_temp || !Object.keys(_temp).length) {
|
|
932
817
|
return _temp;
|
|
@@ -939,7 +824,7 @@ async function handleIncludeBody({ scope, args, body, absolute }) {
|
|
|
939
824
|
});
|
|
940
825
|
};
|
|
941
826
|
|
|
942
|
-
return loopTemplate(
|
|
827
|
+
return loopTemplate(rootTemplate).then(async (temp) => {
|
|
943
828
|
if (!args.query) {
|
|
944
829
|
return temp;
|
|
945
830
|
}
|
|
@@ -950,9 +835,13 @@ async function handleIncludeBody({ scope, args, body, absolute }) {
|
|
|
950
835
|
base: parseLocation(absolute),
|
|
951
836
|
scope,
|
|
952
837
|
cft: args.query,
|
|
838
|
+
caller,
|
|
839
|
+
rootTemplate,
|
|
953
840
|
doEnv: args.doEnv,
|
|
954
841
|
doLog: args.doLog,
|
|
955
842
|
inject: args.inject,
|
|
843
|
+
refNowIgnores: args.refNowIgnores,
|
|
844
|
+
refNowIgnoreMissing: args.refNowIgnoreMissing,
|
|
956
845
|
});
|
|
957
846
|
return getParser(args.parser)(temp, query);
|
|
958
847
|
});
|
package/lib/internals.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
function getAwsPseudoParameters() {
|
|
2
|
+
return {
|
|
3
|
+
'AWS::AccountId': process.env.AWS_ACCOUNT_ID || process.env.AWS_ACCOUNT_NUM || '${AWS::AccountId}',
|
|
4
|
+
'AWS::Partition': process.env.AWS_PARTITION || 'aws',
|
|
5
|
+
'AWS::Region': process.env.AWS_REGION || '${AWS::Region}',
|
|
6
|
+
'AWS::StackId': process.env.AWS_STACK_ID || '${AWS::StackId}',
|
|
7
|
+
'AWS::StackName': process.env.AWS_STACK_NAME || '${AWS::StackName}',
|
|
8
|
+
'AWS::URLSuffix': process.env.AWS_URL_SUFFIX || 'amazonaws.com',
|
|
9
|
+
'AWS::NotificationARNs': process.env.AWS_NOTIFICATION_ARNS || '${AWS::NotificationARNs}',
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Build an ARN for a CloudFormation resource based on its Type and Properties
|
|
15
|
+
* Supports both ARN construction and property name resolution
|
|
16
|
+
* @param {string} resourceType - The CloudFormation resource type (e.g., 'AWS::IAM::ManagedPolicy')
|
|
17
|
+
* @param {object} properties - The resource properties
|
|
18
|
+
* @param {object} pseudoParams - AWS pseudo-parameters including AccountId, Region
|
|
19
|
+
* @param {object} options - Optional configuration
|
|
20
|
+
* @param {string} options.returnType - 'arn' (default) or 'name' to return the resource name/identifier
|
|
21
|
+
* @returns {string|null} - The ARN, name, or null if not applicable
|
|
22
|
+
*/
|
|
23
|
+
function buildResourceArn(resourceType, properties = {}, pseudoParams = {}, options = {}) {
|
|
24
|
+
const accountId = pseudoParams['AWS::AccountId'] || '${AWS::AccountId}';
|
|
25
|
+
const region = pseudoParams['AWS::Region'] || '${AWS::Region}';
|
|
26
|
+
const partition = pseudoParams['AWS::Partition'] || 'aws';
|
|
27
|
+
const returnType = options.returnType || 'arn';
|
|
28
|
+
|
|
29
|
+
// Handle AWS::IAM::ManagedPolicy
|
|
30
|
+
if (resourceType === 'AWS::IAM::ManagedPolicy') {
|
|
31
|
+
const { ManagedPolicyName, Path } = properties;
|
|
32
|
+
if (ManagedPolicyName) {
|
|
33
|
+
if (returnType === 'name') {
|
|
34
|
+
return ManagedPolicyName;
|
|
35
|
+
}
|
|
36
|
+
const path = Path || '/';
|
|
37
|
+
return `arn:${partition}:iam::${accountId}:policy${path}${ManagedPolicyName}`;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Handle AWS::IAM::Role
|
|
42
|
+
if (resourceType === 'AWS::IAM::Role') {
|
|
43
|
+
const { RoleName, Path } = properties;
|
|
44
|
+
if (RoleName) {
|
|
45
|
+
if (returnType === 'name') {
|
|
46
|
+
return RoleName;
|
|
47
|
+
}
|
|
48
|
+
const path = Path || '/';
|
|
49
|
+
return `arn:${partition}:iam::${accountId}:role${path}${RoleName}`;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Handle AWS::S3::Bucket
|
|
54
|
+
if (resourceType === 'AWS::S3::Bucket') {
|
|
55
|
+
const { BucketName } = properties;
|
|
56
|
+
if (BucketName) {
|
|
57
|
+
if (returnType === 'name') {
|
|
58
|
+
return BucketName;
|
|
59
|
+
}
|
|
60
|
+
return `arn:${partition}:s3:::${BucketName}`;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Handle AWS::Lambda::Function
|
|
65
|
+
if (resourceType === 'AWS::Lambda::Function') {
|
|
66
|
+
const { FunctionName } = properties;
|
|
67
|
+
if (FunctionName) {
|
|
68
|
+
if (returnType === 'name') {
|
|
69
|
+
return FunctionName;
|
|
70
|
+
}
|
|
71
|
+
return `arn:${partition}:lambda:${region}:${accountId}:function:${FunctionName}`;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Handle AWS::SQS::Queue
|
|
76
|
+
if (resourceType === 'AWS::SQS::Queue') {
|
|
77
|
+
const { QueueName } = properties;
|
|
78
|
+
if (QueueName) {
|
|
79
|
+
if (returnType === 'name') {
|
|
80
|
+
return QueueName;
|
|
81
|
+
}
|
|
82
|
+
return `arn:${partition}:sqs:${region}:${accountId}:${QueueName}`;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Handle AWS::SNS::Topic
|
|
87
|
+
if (resourceType === 'AWS::SNS::Topic') {
|
|
88
|
+
const { TopicName } = properties;
|
|
89
|
+
if (TopicName) {
|
|
90
|
+
if (returnType === 'name') {
|
|
91
|
+
return TopicName;
|
|
92
|
+
}
|
|
93
|
+
return `arn:${partition}:sns:${region}:${accountId}:${TopicName}`;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Handle AWS::DynamoDB::Table
|
|
98
|
+
if (resourceType === 'AWS::DynamoDB::Table') {
|
|
99
|
+
const { TableName } = properties;
|
|
100
|
+
if (TableName) {
|
|
101
|
+
if (returnType === 'name') {
|
|
102
|
+
return TableName;
|
|
103
|
+
}
|
|
104
|
+
return `arn:${partition}:dynamodb:${region}:${accountId}:table/${TableName}`;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Handle AWS::RDS::DBInstance
|
|
109
|
+
if (resourceType === 'AWS::RDS::DBInstance') {
|
|
110
|
+
const { DBInstanceIdentifier } = properties;
|
|
111
|
+
if (DBInstanceIdentifier) {
|
|
112
|
+
if (returnType === 'name') {
|
|
113
|
+
return DBInstanceIdentifier;
|
|
114
|
+
}
|
|
115
|
+
return `arn:${partition}:rds:${region}:${accountId}:db:${DBInstanceIdentifier}`;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Handle AWS::EC2::SecurityGroup
|
|
120
|
+
if (resourceType === 'AWS::EC2::SecurityGroup') {
|
|
121
|
+
const { GroupName } = properties;
|
|
122
|
+
if (GroupName) {
|
|
123
|
+
if (returnType === 'name') {
|
|
124
|
+
return GroupName;
|
|
125
|
+
}
|
|
126
|
+
// Security groups need to be referenced by ID in most cases, not ARN
|
|
127
|
+
// This returns the name for reference purposes
|
|
128
|
+
return GroupName;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Handle AWS::IAM::InstanceProfile
|
|
133
|
+
if (resourceType === 'AWS::IAM::InstanceProfile') {
|
|
134
|
+
const { InstanceProfileName, Path } = properties;
|
|
135
|
+
if (InstanceProfileName) {
|
|
136
|
+
if (returnType === 'name') {
|
|
137
|
+
return InstanceProfileName;
|
|
138
|
+
}
|
|
139
|
+
const path = Path || '/';
|
|
140
|
+
return `arn:${partition}:iam::${accountId}:instance-profile${path}${InstanceProfileName}`;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Handle AWS::KMS::Key
|
|
145
|
+
if (resourceType === 'AWS::KMS::Key') {
|
|
146
|
+
// KMS keys are referenced by KeyId or KeyArn in properties
|
|
147
|
+
const { KeyId } = properties;
|
|
148
|
+
if (KeyId) {
|
|
149
|
+
if (returnType === 'name') {
|
|
150
|
+
return KeyId;
|
|
151
|
+
}
|
|
152
|
+
return `arn:${partition}:kms:${region}:${accountId}:key/${KeyId}`;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Handle AWS::SecretsManager::Secret
|
|
157
|
+
if (resourceType === 'AWS::SecretsManager::Secret') {
|
|
158
|
+
const { Name } = properties;
|
|
159
|
+
if (Name) {
|
|
160
|
+
if (returnType === 'name') {
|
|
161
|
+
return Name;
|
|
162
|
+
}
|
|
163
|
+
return `arn:${partition}:secretsmanager:${region}:${accountId}:secret:${Name}`;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Add more resource types as needed
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
module.exports = {
|
|
172
|
+
getAwsPseudoParameters,
|
|
173
|
+
buildResourceArn,
|
|
174
|
+
};
|