@znemz/cfn-include 2.1.11 → 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 +174 -1
- package/index.js +151 -63
- 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
|
```
|
|
@@ -1201,6 +1203,61 @@ If an AWS pseudo-parameter is not set via environment variables, it falls back t
|
|
|
1201
1203
|
**Error handling:**
|
|
1202
1204
|
If a reference cannot be resolved, `Fn::RefNow` will throw an error. Ensure all referenced parameters and variables are available via inject, scope, or environment variables.
|
|
1203
1205
|
|
|
1206
|
+
**Resolving LogicalResourceIds:**
|
|
1207
|
+
|
|
1208
|
+
`Fn::RefNow` can also resolve references to CloudFormation Resource LogicalResourceIds, enabling you to construct ARNs or other resource-specific values during template preprocessing. When a reference matches a LogicalResourceId in the Resources section, `Fn::RefNow` will automatically generate the appropriate ARN based on the resource type and properties.
|
|
1209
|
+
|
|
1210
|
+
**Supported Resource Types for ARN/Name Resolution:**
|
|
1211
|
+
|
|
1212
|
+
- `AWS::IAM::ManagedPolicy` - Returns policy ARN (supports Path)
|
|
1213
|
+
- `AWS::IAM::Role` - Returns role ARN (supports Path)
|
|
1214
|
+
- `AWS::IAM::InstanceProfile` - Returns instance profile ARN (supports Path)
|
|
1215
|
+
- `AWS::S3::Bucket` - Returns bucket ARN
|
|
1216
|
+
- `AWS::Lambda::Function` - Returns function ARN
|
|
1217
|
+
- `AWS::SQS::Queue` - Returns queue ARN
|
|
1218
|
+
- `AWS::SNS::Topic` - Returns topic ARN
|
|
1219
|
+
- `AWS::DynamoDB::Table` - Returns table ARN
|
|
1220
|
+
- `AWS::RDS::DBInstance` - Returns DB instance ARN
|
|
1221
|
+
- `AWS::SecretsManager::Secret` - Returns secret ARN
|
|
1222
|
+
- `AWS::KMS::Key` - Returns key ARN
|
|
1223
|
+
|
|
1224
|
+
Example with AWS::IAM::ManagedPolicy:
|
|
1225
|
+
|
|
1226
|
+
```yaml
|
|
1227
|
+
Resources:
|
|
1228
|
+
ObjPolicy:
|
|
1229
|
+
Type: AWS::IAM::ManagedPolicy
|
|
1230
|
+
Properties:
|
|
1231
|
+
ManagedPolicyName: teststack-CreateTestDBPolicy-16M23YE3CS700
|
|
1232
|
+
Path: /CRAP/
|
|
1233
|
+
|
|
1234
|
+
IAMRole:
|
|
1235
|
+
Type: AWS::IAM::Role
|
|
1236
|
+
Properties:
|
|
1237
|
+
ManagedPolicyArns:
|
|
1238
|
+
- Fn::RefNow: ObjPolicy # Resolves to: arn:aws:iam::${AWS_ACCOUNT_ID}:policy/CRAP/teststack-CreateTestDBPolicy-16M23YE3CS700
|
|
1239
|
+
```
|
|
1240
|
+
|
|
1241
|
+
**Returning Resource Names Instead of ARNs:**
|
|
1242
|
+
|
|
1243
|
+
By default, `Fn::RefNow` returns the ARN for supported resource types. However, if the key name ends with `Name` (e.g., `RoleName`, `BucketName`, `FunctionName`), it automatically returns the resource name/identifier instead:
|
|
1244
|
+
|
|
1245
|
+
```yaml
|
|
1246
|
+
Resources:
|
|
1247
|
+
MyRole:
|
|
1248
|
+
Type: AWS::IAM::Role
|
|
1249
|
+
Properties:
|
|
1250
|
+
RoleName: MyExecutionRole
|
|
1251
|
+
|
|
1252
|
+
RoleArn:
|
|
1253
|
+
Fn::RefNow: MyRole # Returns: arn:aws:iam::${AWS_ACCOUNT_ID}:role/MyExecutionRole
|
|
1254
|
+
|
|
1255
|
+
RoleName:
|
|
1256
|
+
Fn::RefNow: MyRole # Returns: MyExecutionRole (because key ends with "Name")
|
|
1257
|
+
```
|
|
1258
|
+
|
|
1259
|
+
This intuitive approach makes templates more readable and follows natural CloudFormation naming conventions.
|
|
1260
|
+
|
|
1204
1261
|
**CLI Options for Fn::RefNow:**
|
|
1205
1262
|
|
|
1206
1263
|
The `cfn-include` command provides CLI options to control how unresolved references are handled:
|
|
@@ -1315,6 +1372,122 @@ Options are query parameters.
|
|
|
1315
1372
|
|
|
1316
1373
|
- `validate=false` do not validate template [true]
|
|
1317
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
|
+
|
|
1318
1491
|
To compile the synopsis run the following command.
|
|
1319
1492
|
|
|
1320
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({
|
|
@@ -59,7 +63,14 @@ module.exports = async function (options) {
|
|
|
59
63
|
template = _.isUndefined(template)
|
|
60
64
|
? fnInclude({ base, scope, cft: options.url, ...options })
|
|
61
65
|
: template;
|
|
62
|
-
|
|
66
|
+
// Resolve template if it's a promise to extract the root template for reference lookups
|
|
67
|
+
const resolvedTemplate = await Promise.resolve(template);
|
|
68
|
+
return recurse({
|
|
69
|
+
base, scope,
|
|
70
|
+
cft: resolvedTemplate,
|
|
71
|
+
rootTemplate: resolvedTemplate,
|
|
72
|
+
...options,
|
|
73
|
+
});
|
|
63
74
|
};
|
|
64
75
|
|
|
65
76
|
/**
|
|
@@ -89,27 +100,18 @@ module.exports = async function (options) {
|
|
|
89
100
|
* @param {Object} opts.inject object to inject { KEY: Value } from where ${KEY}
|
|
90
101
|
* is subtituted with Value
|
|
91
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..
|
|
92
107
|
*/
|
|
93
|
-
function
|
|
94
|
-
return {
|
|
95
|
-
'AWS::AccountId': process.env.AWS_ACCOUNT_ID || process.env.AWS_ACCOUNT_NUM || '${AWS::AccountId}',
|
|
96
|
-
'AWS::Partition': process.env.AWS_PARTITION || 'aws',
|
|
97
|
-
'AWS::Region': process.env.AWS_REGION || '${AWS::Region}',
|
|
98
|
-
'AWS::StackId': process.env.AWS_STACK_ID || '${AWS::StackId}',
|
|
99
|
-
'AWS::StackName': process.env.AWS_STACK_NAME || '${AWS::StackName}',
|
|
100
|
-
'AWS::URLSuffix': process.env.AWS_URL_SUFFIX || 'amazonaws.com',
|
|
101
|
-
'AWS::NotificationARNs': process.env.AWS_NOTIFICATION_ARNS || '${AWS::NotificationARNs}',
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
async function recurse({ base, scope, cft, ...opts }) {
|
|
108
|
+
async function recurse({ base, scope, cft, rootTemplate, caller, ...opts }) {
|
|
106
109
|
if (opts.doLog) {
|
|
107
|
-
|
|
108
|
-
console.log({ base, scope, cft, ...opts });
|
|
110
|
+
console.log({ base, scope, cft, rootTemplate, caller, ...opts });
|
|
109
111
|
}
|
|
110
112
|
scope = _.clone(scope);
|
|
111
113
|
if (_.isArray(cft)) {
|
|
112
|
-
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 })));
|
|
113
115
|
}
|
|
114
116
|
if (_.isPlainObject(cft)) {
|
|
115
117
|
if (cft['Fn::Map']) {
|
|
@@ -134,26 +136,26 @@ async function recurse({ base, scope, cft, ...opts }) {
|
|
|
134
136
|
if (args.length === 2) {
|
|
135
137
|
placeholder = '_';
|
|
136
138
|
}
|
|
137
|
-
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) => {
|
|
138
140
|
scope = _.clone(scope);
|
|
139
141
|
scope[placeholder] = replace;
|
|
140
142
|
if (hasindex) {
|
|
141
143
|
scope[idx] = key;
|
|
142
144
|
}
|
|
143
145
|
const replaced = findAndReplace(scope, _.cloneDeep(body));
|
|
144
|
-
return recurse({ base, scope, cft: replaced, ...opts });
|
|
146
|
+
return recurse({ base, scope, cft: replaced, rootTemplate, caller: 'Fn::Map', ...opts });
|
|
145
147
|
}).then((_cft) => {
|
|
146
148
|
if (hassize) {
|
|
147
149
|
_cft = findAndReplace({ [sz]: _cft.length }, _cft);
|
|
148
150
|
}
|
|
149
|
-
return recurse({ base, scope, cft: _cft, ...opts });
|
|
151
|
+
return recurse({ base, scope, cft: _cft, rootTemplate, caller: 'Fn::Map', ...opts });
|
|
150
152
|
});
|
|
151
153
|
}
|
|
152
154
|
if (cft['Fn::Length']) {
|
|
153
155
|
if (Array.isArray(cft['Fn::Length'])) {
|
|
154
156
|
return cft['Fn::Length'].length;
|
|
155
157
|
}
|
|
156
|
-
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) => {
|
|
157
159
|
if (Array.isArray(x)) {
|
|
158
160
|
return x.length;
|
|
159
161
|
}
|
|
@@ -169,49 +171,49 @@ async function recurse({ base, scope, cft, ...opts }) {
|
|
|
169
171
|
return cft;
|
|
170
172
|
})
|
|
171
173
|
.then((_cft) => findAndReplace(scope, _cft))
|
|
172
|
-
.then((t) => recurse({ base, scope, cft: t, ...opts }));
|
|
174
|
+
.then((t) => recurse({ base, scope, cft: t, rootTemplate, caller: 'Fn::Include', ...opts }));
|
|
173
175
|
}
|
|
174
176
|
if (cft['Fn::Flatten']) {
|
|
175
|
-
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) {
|
|
176
178
|
return _.flatten(json);
|
|
177
179
|
});
|
|
178
180
|
}
|
|
179
181
|
if (cft['Fn::FlattenDeep']) {
|
|
180
|
-
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(
|
|
181
183
|
function (json) {
|
|
182
184
|
return _.flattenDeep(json);
|
|
183
185
|
},
|
|
184
186
|
);
|
|
185
187
|
}
|
|
186
188
|
if (cft['Fn::Uniq']) {
|
|
187
|
-
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) {
|
|
188
190
|
return _.uniq(json);
|
|
189
191
|
});
|
|
190
192
|
}
|
|
191
193
|
if (cft['Fn::Compact']) {
|
|
192
|
-
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) {
|
|
193
195
|
return _.compact(json);
|
|
194
196
|
});
|
|
195
197
|
}
|
|
196
198
|
if (cft['Fn::Concat']) {
|
|
197
|
-
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) {
|
|
198
200
|
return _.concat(...json);
|
|
199
201
|
});
|
|
200
202
|
}
|
|
201
203
|
if (cft['Fn::Sort']) {
|
|
202
|
-
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) {
|
|
203
205
|
return array.sort();
|
|
204
206
|
});
|
|
205
207
|
}
|
|
206
208
|
if (cft['Fn::SortedUniq']) {
|
|
207
|
-
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(
|
|
208
210
|
function (array) {
|
|
209
211
|
return _.sortedUniq(array.sort());
|
|
210
212
|
},
|
|
211
213
|
);
|
|
212
214
|
}
|
|
213
215
|
if (cft['Fn::SortBy']) {
|
|
214
|
-
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 ({
|
|
215
217
|
list,
|
|
216
218
|
iteratees,
|
|
217
219
|
}) {
|
|
@@ -219,7 +221,7 @@ async function recurse({ base, scope, cft, ...opts }) {
|
|
|
219
221
|
});
|
|
220
222
|
}
|
|
221
223
|
if (cft['Fn::SortObject']) {
|
|
222
|
-
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 ({
|
|
223
225
|
|
|
224
226
|
object,
|
|
225
227
|
options,
|
|
@@ -229,19 +231,19 @@ async function recurse({ base, scope, cft, ...opts }) {
|
|
|
229
231
|
});
|
|
230
232
|
}
|
|
231
233
|
if (cft['Fn::Without']) {
|
|
232
|
-
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) {
|
|
233
235
|
json = Array.isArray(json) ? { list: json[0], withouts: json[1] } : json;
|
|
234
236
|
return _.without(json.list, ...json.withouts);
|
|
235
237
|
});
|
|
236
238
|
}
|
|
237
239
|
if (cft['Fn::Omit']) {
|
|
238
|
-
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) {
|
|
239
241
|
json = Array.isArray(json) ? { object: json[0], omits: json[1] } : json;
|
|
240
242
|
return _.omit(json.object, json.omits);
|
|
241
243
|
});
|
|
242
244
|
}
|
|
243
245
|
if (cft['Fn::OmitEmpty']) {
|
|
244
|
-
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(
|
|
245
247
|
function (json) {
|
|
246
248
|
// omit falsy values except false, and 0
|
|
247
249
|
return _.omitBy(json, (v) => !v && v !== false && v !== 0);
|
|
@@ -252,7 +254,7 @@ async function recurse({ base, scope, cft, ...opts }) {
|
|
|
252
254
|
if (!opts.doEval) {
|
|
253
255
|
return Promise.reject(new Error('Fn::Eval is not allowed doEval is falsy'));
|
|
254
256
|
}
|
|
255
|
-
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) {
|
|
256
258
|
// **WARNING** you have now enabled god mode
|
|
257
259
|
|
|
258
260
|
let { state, script, inject, doLog } = json;
|
|
@@ -266,7 +268,7 @@ async function recurse({ base, scope, cft, ...opts }) {
|
|
|
266
268
|
});
|
|
267
269
|
}
|
|
268
270
|
if (cft['Fn::Filenames']) {
|
|
269
|
-
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(
|
|
270
272
|
function (json) {
|
|
271
273
|
json = _.isPlainObject(json) ? { ...json } : { location: json };
|
|
272
274
|
if (json.doLog) {
|
|
@@ -293,14 +295,14 @@ async function recurse({ base, scope, cft, ...opts }) {
|
|
|
293
295
|
);
|
|
294
296
|
}
|
|
295
297
|
if (cft['Fn::Merge']) {
|
|
296
|
-
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) {
|
|
297
299
|
delete cft['Fn::Merge'];
|
|
298
300
|
|
|
299
|
-
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 });
|
|
300
302
|
});
|
|
301
303
|
}
|
|
302
304
|
if (cft['Fn::DeepMerge']) {
|
|
303
|
-
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(
|
|
304
306
|
function (json) {
|
|
305
307
|
delete cft['Fn::DeepMerge'];
|
|
306
308
|
let mergedObj = {};
|
|
@@ -309,29 +311,29 @@ async function recurse({ base, scope, cft, ...opts }) {
|
|
|
309
311
|
mergedObj = deepMerge(mergedObj, j);
|
|
310
312
|
});
|
|
311
313
|
}
|
|
312
|
-
return recurse({ base, scope, cft: _.defaults(cft, mergedObj), ...opts });
|
|
314
|
+
return recurse({ base, scope, cft: _.defaults(cft, mergedObj), rootTemplate, caller: 'Fn::DeepMerge', ...opts });
|
|
313
315
|
},
|
|
314
316
|
);
|
|
315
317
|
}
|
|
316
318
|
if (cft['Fn::ObjectKeys']) {
|
|
317
|
-
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) =>
|
|
318
320
|
Object.keys(json),
|
|
319
321
|
);
|
|
320
322
|
}
|
|
321
323
|
if (cft['Fn::ObjectValues']) {
|
|
322
|
-
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) =>
|
|
323
325
|
Object.values(json),
|
|
324
326
|
);
|
|
325
327
|
}
|
|
326
328
|
if (cft['Fn::Stringify']) {
|
|
327
|
-
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(
|
|
328
330
|
function (json) {
|
|
329
331
|
return JSON.stringify(json);
|
|
330
332
|
},
|
|
331
333
|
);
|
|
332
334
|
}
|
|
333
335
|
if (cft['Fn::StringSplit']) {
|
|
334
|
-
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(
|
|
335
337
|
({ string, separator, doLog }) => {
|
|
336
338
|
if (!string) {
|
|
337
339
|
string = '';
|
|
@@ -366,7 +368,7 @@ async function recurse({ base, scope, cft, ...opts }) {
|
|
|
366
368
|
}
|
|
367
369
|
|
|
368
370
|
if (cft['Fn::Outputs']) {
|
|
369
|
-
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 });
|
|
370
372
|
const result = {};
|
|
371
373
|
|
|
372
374
|
for (const output in outputs) {
|
|
@@ -391,7 +393,7 @@ async function recurse({ base, scope, cft, ...opts }) {
|
|
|
391
393
|
}
|
|
392
394
|
|
|
393
395
|
if (cft['Fn::Sequence']) {
|
|
394
|
-
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 });
|
|
395
397
|
|
|
396
398
|
let [start, stop, step = 1] = outputs;
|
|
397
399
|
const isString = typeof start === 'string';
|
|
@@ -410,7 +412,7 @@ async function recurse({ base, scope, cft, ...opts }) {
|
|
|
410
412
|
if (!opts.doEval) {
|
|
411
413
|
return Promise.reject(new Error('Fn::IfEval is not allowed doEval is falsy'));
|
|
412
414
|
}
|
|
413
|
-
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) {
|
|
414
416
|
|
|
415
417
|
let { truthy, falsy, evalCond, inject, doLog } = json;
|
|
416
418
|
if (!evalCond) {
|
|
@@ -439,13 +441,13 @@ async function recurse({ base, scope, cft, ...opts }) {
|
|
|
439
441
|
}
|
|
440
442
|
|
|
441
443
|
if (condResult) {
|
|
442
|
-
return recurse({ base, scope, cft: truthy, ...opts });
|
|
444
|
+
return recurse({ base, scope, cft: truthy, rootTemplate, caller: 'Fn::IfEval', ...opts });
|
|
443
445
|
}
|
|
444
|
-
return recurse({ base, scope, cft: falsy, ...opts });
|
|
446
|
+
return recurse({ base, scope, cft: falsy, rootTemplate, caller: 'Fn::IfEval', ...opts });
|
|
445
447
|
});
|
|
446
448
|
}
|
|
447
449
|
if (cft['Fn::JoinNow']) {
|
|
448
|
-
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) => {
|
|
449
451
|
// keeps with same format as Fn::Join ~ more complex
|
|
450
452
|
// vs let [delimitter, ...toJoinArray] = array;
|
|
451
453
|
let [delimitter, toJoinArray] = array;
|
|
@@ -454,7 +456,7 @@ async function recurse({ base, scope, cft, ...opts }) {
|
|
|
454
456
|
});
|
|
455
457
|
}
|
|
456
458
|
if (cft['Fn::SubNow']) {
|
|
457
|
-
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) => {
|
|
458
460
|
let template = input;
|
|
459
461
|
let variables = {};
|
|
460
462
|
|
|
@@ -481,8 +483,21 @@ async function recurse({ base, scope, cft, ...opts }) {
|
|
|
481
483
|
});
|
|
482
484
|
}
|
|
483
485
|
if (cft['Fn::RefNow']) {
|
|
484
|
-
|
|
485
|
-
|
|
486
|
+
// console.log("outter");
|
|
487
|
+
// console.log(opts);
|
|
488
|
+
// Handle both simple string and object format for Fn::RefNow
|
|
489
|
+
// e.g., Fn::RefNow: "MyRole" or Fn::RefNow: { Ref: "MyRole" }
|
|
490
|
+
return recurse({ base, scope, cft: cft['Fn::RefNow'], rootTemplate, caller: 'Fn::RefNow', ...opts }).then((refInput) => {
|
|
491
|
+
// console.log("inner");
|
|
492
|
+
// console.log(opts);
|
|
493
|
+
let refName = refInput;
|
|
494
|
+
let refOptions = {};
|
|
495
|
+
|
|
496
|
+
// Parse the input - could be string or object
|
|
497
|
+
if (_.isPlainObject(refInput)) {
|
|
498
|
+
refName = refInput.Ref || refInput.ref;
|
|
499
|
+
refOptions = _.omit(refInput, ['Ref', 'ref']);
|
|
500
|
+
}
|
|
486
501
|
|
|
487
502
|
// Check if this ref name is in the ignores list
|
|
488
503
|
if (opts.refNowIgnores && opts.refNowIgnores.includes(refName)) {
|
|
@@ -502,6 +517,36 @@ async function recurse({ base, scope, cft, ...opts }) {
|
|
|
502
517
|
return allRefs[refName];
|
|
503
518
|
}
|
|
504
519
|
|
|
520
|
+
// Check if this is a LogicalResourceId in the Resources section
|
|
521
|
+
if (rootTemplate && rootTemplate.Resources) {
|
|
522
|
+
const resources = rootTemplate.Resources;
|
|
523
|
+
if (refName in resources) {
|
|
524
|
+
const resource = resources[refName];
|
|
525
|
+
const resourceType = resource.Type;
|
|
526
|
+
const properties = resource.Properties || {};
|
|
527
|
+
|
|
528
|
+
// Determine return type based on key name
|
|
529
|
+
// If the key ends with "Name" (e.g., RoleName, BucketName), return name/identifier
|
|
530
|
+
// Otherwise return ARN (default)
|
|
531
|
+
let returnType = 'arn';
|
|
532
|
+
if (opts.key && opts.key.endsWith('Name')) {
|
|
533
|
+
returnType = 'name';
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Try to build an ARN or name for this resource
|
|
537
|
+
// Merge ref options with global options (ref options take precedence)
|
|
538
|
+
const resourceOptions = {
|
|
539
|
+
returnType,
|
|
540
|
+
...opts.refNowReturnType ? { returnType: opts.refNowReturnType } : {},
|
|
541
|
+
...refOptions,
|
|
542
|
+
};
|
|
543
|
+
const result = buildResourceArn(resourceType, properties, allRefs, resourceOptions);
|
|
544
|
+
if (result) {
|
|
545
|
+
return result;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
505
550
|
// If not found and refNowIgnoreMissing is true, return Ref syntax; otherwise throw error
|
|
506
551
|
if (opts.refNowIgnoreMissing) {
|
|
507
552
|
return { Ref: refName };
|
|
@@ -512,7 +557,7 @@ async function recurse({ base, scope, cft, ...opts }) {
|
|
|
512
557
|
});
|
|
513
558
|
}
|
|
514
559
|
if (cft['Fn::ApplyTags']) {
|
|
515
|
-
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) => {
|
|
516
561
|
let { tags, Tags, resources } = json;
|
|
517
562
|
tags = tags || Tags; // allow for both caseing
|
|
518
563
|
const promises = [];
|
|
@@ -538,7 +583,7 @@ async function recurse({ base, scope, cft, ...opts }) {
|
|
|
538
583
|
}
|
|
539
584
|
|
|
540
585
|
return Promise.props(
|
|
541
|
-
_.mapValues(cft, (template) => recurse({ base, scope, cft: template, ...opts })),
|
|
586
|
+
_.mapValues(cft, (template, key) => recurse({ base, scope, cft: template, key, rootTemplate, caller: 'recurse:isPlainObject:end', ...opts })),
|
|
542
587
|
);
|
|
543
588
|
}
|
|
544
589
|
|
|
@@ -632,6 +677,21 @@ function fnIncludeOpts(cft, opts) {
|
|
|
632
677
|
return cft;
|
|
633
678
|
}
|
|
634
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
|
+
*/
|
|
635
695
|
async function fnInclude({ base, scope, cft, ...opts }) {
|
|
636
696
|
let procTemplate = async (template, inject = cft.inject, doEnv = opts.doEnv) =>
|
|
637
697
|
replaceEnv(template, inject, doEnv);
|
|
@@ -653,7 +713,6 @@ async function fnInclude({ base, scope, cft, ...opts }) {
|
|
|
653
713
|
cft = fnIncludeOpts(cft, opts);
|
|
654
714
|
|
|
655
715
|
if (cft.doLog) {
|
|
656
|
-
|
|
657
716
|
console.log({ base, scope, args: cft, ...opts });
|
|
658
717
|
}
|
|
659
718
|
// console.log(args)
|
|
@@ -673,14 +732,14 @@ async function fnInclude({ base, scope, cft, ...opts }) {
|
|
|
673
732
|
if (isGlob(cft, absolute)) {
|
|
674
733
|
const paths = globSync(absolute).sort();
|
|
675
734
|
const template = yaml.load(paths.map((_p) => `- Fn::Include: file://${_p}`).join('\n'));
|
|
676
|
-
return recurse({ base, scope, cft: template, ...opts });
|
|
735
|
+
return recurse({ base, scope, cft: template, rootTemplate: template, ...opts });
|
|
677
736
|
}
|
|
678
737
|
body = readFile(absolute).then(String).then(procTemplate);
|
|
679
738
|
absolute = `${location.protocol}://${absolute}`;
|
|
680
739
|
} else if (location.protocol === 's3') {
|
|
681
740
|
const basedir = pathParse(base.path).dir;
|
|
682
741
|
const bucket = location.relative ? base.host : location.host;
|
|
683
|
-
|
|
742
|
+
|
|
684
743
|
let key = location.relative ? url.resolve(`${basedir}/`, location.raw) : location.path;
|
|
685
744
|
key = key.replace(/^\//, '');
|
|
686
745
|
absolute = `${location.protocol}://${[bucket, key].join('/')}`;
|
|
@@ -695,11 +754,11 @@ async function fnInclude({ base, scope, cft, ...opts }) {
|
|
|
695
754
|
.then(procTemplate);
|
|
696
755
|
} else if (location.protocol && location.protocol.match(/^https?$/)) {
|
|
697
756
|
const basepath = `${pathParse(base.path).dir}/`;
|
|
698
|
-
|
|
757
|
+
|
|
699
758
|
absolute = location.relative
|
|
700
759
|
? url.resolve(`${location.protocol}://${base.host}${basepath}`, location.raw)
|
|
701
760
|
: location.raw;
|
|
702
|
-
|
|
761
|
+
|
|
703
762
|
body = request(absolute).then(procTemplate);
|
|
704
763
|
}
|
|
705
764
|
return handleIncludeBody({ scope, args: cft, body, absolute });
|
|
@@ -709,6 +768,27 @@ function isGlob(args, str) {
|
|
|
709
768
|
return args.isGlob || /.*\*/.test(str);
|
|
710
769
|
}
|
|
711
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
|
+
*/
|
|
712
792
|
async function handleIncludeBody({ scope, args, body, absolute }) {
|
|
713
793
|
const procTemplate = (temp) => replaceEnv(temp, args.inject, args.doEnv);
|
|
714
794
|
try {
|
|
@@ -716,18 +796,22 @@ async function handleIncludeBody({ scope, args, body, absolute }) {
|
|
|
716
796
|
case 'json': {
|
|
717
797
|
let b = await body;
|
|
718
798
|
b = procTemplate(b);
|
|
719
|
-
const
|
|
720
|
-
|
|
799
|
+
const rootTemplate = yaml.load(b);
|
|
800
|
+
const caller = 'handleIncludeBody:json';
|
|
721
801
|
// recurse all the way down and work your way out
|
|
722
802
|
const loopTemplate = (temp) => {
|
|
723
803
|
return recurse({
|
|
724
804
|
base: parseLocation(absolute),
|
|
725
805
|
scope,
|
|
726
806
|
cft: temp,
|
|
807
|
+
caller,
|
|
808
|
+
rootTemplate,
|
|
727
809
|
doEnv: args.doEnv,
|
|
728
810
|
doEval: args.doEval,
|
|
729
811
|
doLog: args.doLog,
|
|
730
812
|
inject: args.inject,
|
|
813
|
+
refNowIgnores: args.refNowIgnores,
|
|
814
|
+
refNowIgnoreMissing: args.refNowIgnoreMissing, // note we can't use ...args here because of circular ref
|
|
731
815
|
}).then((_temp) => {
|
|
732
816
|
if (!_temp || !Object.keys(_temp).length) {
|
|
733
817
|
return _temp;
|
|
@@ -740,7 +824,7 @@ async function handleIncludeBody({ scope, args, body, absolute }) {
|
|
|
740
824
|
});
|
|
741
825
|
};
|
|
742
826
|
|
|
743
|
-
return loopTemplate(
|
|
827
|
+
return loopTemplate(rootTemplate).then(async (temp) => {
|
|
744
828
|
if (!args.query) {
|
|
745
829
|
return temp;
|
|
746
830
|
}
|
|
@@ -751,9 +835,13 @@ async function handleIncludeBody({ scope, args, body, absolute }) {
|
|
|
751
835
|
base: parseLocation(absolute),
|
|
752
836
|
scope,
|
|
753
837
|
cft: args.query,
|
|
838
|
+
caller,
|
|
839
|
+
rootTemplate,
|
|
754
840
|
doEnv: args.doEnv,
|
|
755
841
|
doLog: args.doLog,
|
|
756
842
|
inject: args.inject,
|
|
843
|
+
refNowIgnores: args.refNowIgnores,
|
|
844
|
+
refNowIgnoreMissing: args.refNowIgnoreMissing,
|
|
757
845
|
});
|
|
758
846
|
return getParser(args.parser)(temp, query);
|
|
759
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
|
+
};
|