@znemz/cfn-include 2.1.11 → 2.1.12

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.
Files changed (3) hide show
  1. package/README.md +55 -0
  2. package/index.js +203 -4
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1201,6 +1201,61 @@ If an AWS pseudo-parameter is not set via environment variables, it falls back t
1201
1201
  **Error handling:**
1202
1202
  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
1203
 
1204
+ **Resolving LogicalResourceIds:**
1205
+
1206
+ `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.
1207
+
1208
+ **Supported Resource Types for ARN/Name Resolution:**
1209
+
1210
+ - `AWS::IAM::ManagedPolicy` - Returns policy ARN (supports Path)
1211
+ - `AWS::IAM::Role` - Returns role ARN (supports Path)
1212
+ - `AWS::IAM::InstanceProfile` - Returns instance profile ARN (supports Path)
1213
+ - `AWS::S3::Bucket` - Returns bucket ARN
1214
+ - `AWS::Lambda::Function` - Returns function ARN
1215
+ - `AWS::SQS::Queue` - Returns queue ARN
1216
+ - `AWS::SNS::Topic` - Returns topic ARN
1217
+ - `AWS::DynamoDB::Table` - Returns table ARN
1218
+ - `AWS::RDS::DBInstance` - Returns DB instance ARN
1219
+ - `AWS::SecretsManager::Secret` - Returns secret ARN
1220
+ - `AWS::KMS::Key` - Returns key ARN
1221
+
1222
+ Example with AWS::IAM::ManagedPolicy:
1223
+
1224
+ ```yaml
1225
+ Resources:
1226
+ ObjPolicy:
1227
+ Type: AWS::IAM::ManagedPolicy
1228
+ Properties:
1229
+ ManagedPolicyName: teststack-CreateTestDBPolicy-16M23YE3CS700
1230
+ Path: /CRAP/
1231
+
1232
+ IAMRole:
1233
+ Type: AWS::IAM::Role
1234
+ Properties:
1235
+ ManagedPolicyArns:
1236
+ - Fn::RefNow: ObjPolicy # Resolves to: arn:aws:iam::${AWS_ACCOUNT_ID}:policy/CRAP/teststack-CreateTestDBPolicy-16M23YE3CS700
1237
+ ```
1238
+
1239
+ **Returning Resource Names Instead of ARNs:**
1240
+
1241
+ 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:
1242
+
1243
+ ```yaml
1244
+ Resources:
1245
+ MyRole:
1246
+ Type: AWS::IAM::Role
1247
+ Properties:
1248
+ RoleName: MyExecutionRole
1249
+
1250
+ RoleArn:
1251
+ Fn::RefNow: MyRole # Returns: arn:aws:iam::${AWS_ACCOUNT_ID}:role/MyExecutionRole
1252
+
1253
+ RoleName:
1254
+ Fn::RefNow: MyRole # Returns: MyExecutionRole (because key ends with "Name")
1255
+ ```
1256
+
1257
+ This intuitive approach makes templates more readable and follows natural CloudFormation naming conventions.
1258
+
1204
1259
  **CLI Options for Fn::RefNow:**
1205
1260
 
1206
1261
  The `cfn-include` command provides CLI options to control how unresolved references are handled:
package/index.js CHANGED
@@ -59,7 +59,9 @@ module.exports = async function (options) {
59
59
  template = _.isUndefined(template)
60
60
  ? fnInclude({ base, scope, cft: options.url, ...options })
61
61
  : template;
62
- return recurse({ base, scope, cft: template, ...options });
62
+ // Resolve template if it's a promise to extract the root template for reference lookups
63
+ const resolvedTemplate = await Promise.resolve(template);
64
+ return recurse({ base, scope, cft: resolvedTemplate, rootTemplate: resolvedTemplate, ...options });
63
65
  };
64
66
 
65
67
  /**
@@ -102,6 +104,164 @@ function getAwsPseudoParameters() {
102
104
  };
103
105
  }
104
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
+
105
265
  async function recurse({ base, scope, cft, ...opts }) {
106
266
  if (opts.doLog) {
107
267
 
@@ -481,8 +641,17 @@ async function recurse({ base, scope, cft, ...opts }) {
481
641
  });
482
642
  }
483
643
  if (cft['Fn::RefNow']) {
484
- return recurse({ base, scope, cft: cft['Fn::RefNow'], ...opts }).then((logicalName) => {
485
- let refName = logicalName;
644
+ // Handle both simple string and object format for Fn::RefNow
645
+ // e.g., Fn::RefNow: "MyRole" or Fn::RefNow: { Ref: "MyRole" }
646
+ return recurse({ base, scope, cft: cft['Fn::RefNow'], ...opts }).then((refInput) => {
647
+ let refName = refInput;
648
+ let refOptions = {};
649
+
650
+ // Parse the input - could be string or object
651
+ if (_.isPlainObject(refInput)) {
652
+ refName = refInput.Ref || refInput.ref;
653
+ refOptions = _.omit(refInput, ['Ref', 'ref']);
654
+ }
486
655
 
487
656
  // Check if this ref name is in the ignores list
488
657
  if (opts.refNowIgnores && opts.refNowIgnores.includes(refName)) {
@@ -502,6 +671,36 @@ async function recurse({ base, scope, cft, ...opts }) {
502
671
  return allRefs[refName];
503
672
  }
504
673
 
674
+ // Check if this is a LogicalResourceId in the Resources section
675
+ if (opts.rootTemplate && _.isPlainObject(opts.rootTemplate)) {
676
+ const resources = opts.rootTemplate.Resources || {};
677
+ if (refName in resources) {
678
+ const resource = resources[refName];
679
+ const resourceType = resource.Type;
680
+ const properties = resource.Properties || {};
681
+
682
+ // Determine return type based on key name
683
+ // If the key ends with "Name" (e.g., RoleName, BucketName), return name/identifier
684
+ // Otherwise return ARN (default)
685
+ let returnType = 'arn';
686
+ if (opts.key && opts.key.endsWith('Name')) {
687
+ returnType = 'name';
688
+ }
689
+
690
+ // Try to build an ARN or name for this resource
691
+ // Merge ref options with global options (ref options take precedence)
692
+ const resourceOptions = {
693
+ returnType,
694
+ ...opts.refNowReturnType ? { returnType: opts.refNowReturnType } : {},
695
+ ...refOptions,
696
+ };
697
+ const result = buildResourceArn(resourceType, properties, allRefs, resourceOptions);
698
+ if (result) {
699
+ return result;
700
+ }
701
+ }
702
+ }
703
+
505
704
  // If not found and refNowIgnoreMissing is true, return Ref syntax; otherwise throw error
506
705
  if (opts.refNowIgnoreMissing) {
507
706
  return { Ref: refName };
@@ -538,7 +737,7 @@ async function recurse({ base, scope, cft, ...opts }) {
538
737
  }
539
738
 
540
739
  return Promise.props(
541
- _.mapValues(cft, (template) => recurse({ base, scope, cft: template, ...opts })),
740
+ _.mapValues(cft, (template, key) => recurse({ base, scope, cft: template, key, ...opts })),
542
741
  );
543
742
  }
544
743
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@znemz/cfn-include",
3
- "version": "2.1.11",
3
+ "version": "2.1.12",
4
4
  "description": "Preprocessor for CloudFormation templates with support for loops and flexible include statements",
5
5
  "keywords": [
6
6
  "aws",