cfn-check 0.3.2__tar.gz → 0.9.0__tar.gz

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 (87) hide show
  1. {cfn_check-0.3.2 → cfn_check-0.9.0}/PKG-INFO +201 -25
  2. {cfn_check-0.3.2 → cfn_check-0.9.0}/README.md +198 -23
  3. cfn_check-0.9.0/cfn_check/cli/config.py +10 -0
  4. cfn_check-0.9.0/cfn_check/cli/render.py +141 -0
  5. {cfn_check-0.3.2 → cfn_check-0.9.0}/cfn_check/cli/root.py +3 -1
  6. {cfn_check-0.3.2 → cfn_check-0.9.0}/cfn_check/cli/utils/attributes.py +1 -1
  7. {cfn_check-0.3.2 → cfn_check-0.9.0}/cfn_check/cli/utils/files.py +46 -21
  8. cfn_check-0.9.0/cfn_check/cli/utils/stdout.py +18 -0
  9. {cfn_check-0.3.2 → cfn_check-0.9.0}/cfn_check/cli/validate.py +36 -26
  10. cfn_check-0.9.0/cfn_check/collection/collection.py +59 -0
  11. cfn_check-0.9.0/cfn_check/evaluation/evaluator.py +128 -0
  12. cfn_check-0.9.0/cfn_check/evaluation/parsing/operators/__init__.py +1 -0
  13. cfn_check-0.9.0/cfn_check/evaluation/parsing/operators/value_operator.py +118 -0
  14. {cfn_check-0.3.2 → cfn_check-0.9.0}/cfn_check/evaluation/parsing/query_parser.py +26 -2
  15. {cfn_check-0.3.2 → cfn_check-0.9.0}/cfn_check/evaluation/parsing/token.py +124 -67
  16. {cfn_check-0.3.2 → cfn_check-0.9.0}/cfn_check/evaluation/parsing/token_type.py +2 -0
  17. {cfn_check-0.3.2 → cfn_check-0.9.0}/cfn_check/evaluation/validate.py +34 -3
  18. cfn_check-0.9.0/cfn_check/rendering/__init__.py +1 -0
  19. cfn_check-0.9.0/cfn_check/rendering/cidr_solver.py +66 -0
  20. cfn_check-0.9.0/cfn_check/rendering/renderer.py +1316 -0
  21. cfn_check-0.9.0/cfn_check/rendering/utils.py +13 -0
  22. {cfn_check-0.3.2 → cfn_check-0.9.0}/cfn_check/rules/rule.py +3 -0
  23. cfn_check-0.9.0/cfn_check/validation/__init__.py +0 -0
  24. {cfn_check-0.3.2 → cfn_check-0.9.0}/cfn_check/validation/validator.py +11 -1
  25. cfn_check-0.9.0/cfn_check/yaml/__init__.py +5 -0
  26. cfn_check-0.9.0/cfn_check/yaml/anchor.py +18 -0
  27. cfn_check-0.9.0/cfn_check/yaml/comments.py +1201 -0
  28. cfn_check-0.9.0/cfn_check/yaml/compat.py +235 -0
  29. cfn_check-0.9.0/cfn_check/yaml/composer.py +229 -0
  30. cfn_check-0.9.0/cfn_check/yaml/configobjwalker.py +15 -0
  31. cfn_check-0.9.0/cfn_check/yaml/constructor.py +1722 -0
  32. cfn_check-0.9.0/cfn_check/yaml/cyaml.py +196 -0
  33. cfn_check-0.9.0/cfn_check/yaml/docinfo.py +115 -0
  34. cfn_check-0.9.0/cfn_check/yaml/dumper.py +216 -0
  35. cfn_check-0.9.0/cfn_check/yaml/emitter.py +1813 -0
  36. cfn_check-0.9.0/cfn_check/yaml/error.py +314 -0
  37. cfn_check-0.9.0/cfn_check/yaml/events.py +265 -0
  38. cfn_check-0.9.0/cfn_check/yaml/loader.py +91 -0
  39. cfn_check-0.9.0/cfn_check/yaml/main.py +1521 -0
  40. cfn_check-0.9.0/cfn_check/yaml/mergevalue.py +37 -0
  41. cfn_check-0.9.0/cfn_check/yaml/nodes.py +148 -0
  42. cfn_check-0.9.0/cfn_check/yaml/parser.py +909 -0
  43. cfn_check-0.9.0/cfn_check/yaml/reader.py +274 -0
  44. cfn_check-0.9.0/cfn_check/yaml/representer.py +1146 -0
  45. cfn_check-0.9.0/cfn_check/yaml/resolver.py +390 -0
  46. cfn_check-0.9.0/cfn_check/yaml/scalarbool.py +41 -0
  47. cfn_check-0.9.0/cfn_check/yaml/scalarfloat.py +103 -0
  48. cfn_check-0.9.0/cfn_check/yaml/scalarint.py +122 -0
  49. cfn_check-0.9.0/cfn_check/yaml/scalarstring.py +140 -0
  50. cfn_check-0.9.0/cfn_check/yaml/scanner.py +2390 -0
  51. cfn_check-0.9.0/cfn_check/yaml/serializer.py +231 -0
  52. cfn_check-0.9.0/cfn_check/yaml/tag.py +124 -0
  53. cfn_check-0.9.0/cfn_check/yaml/timestamp.py +61 -0
  54. cfn_check-0.9.0/cfn_check/yaml/tokens.py +382 -0
  55. cfn_check-0.9.0/cfn_check/yaml/util.py +262 -0
  56. {cfn_check-0.3.2 → cfn_check-0.9.0}/cfn_check.egg-info/PKG-INFO +201 -25
  57. cfn_check-0.9.0/cfn_check.egg-info/SOURCES.txt +81 -0
  58. {cfn_check-0.3.2 → cfn_check-0.9.0}/cfn_check.egg-info/requires.txt +1 -1
  59. cfn_check-0.9.0/example/pydantic_rules.py +35 -0
  60. cfn_check-0.9.0/example/renderer_test.py +42 -0
  61. {cfn_check-0.3.2 → cfn_check-0.9.0}/example/rules.py +3 -3
  62. cfn_check-0.9.0/example/test_models/__init__.py +0 -0
  63. cfn_check-0.9.0/example/test_models/models.py +54 -0
  64. {cfn_check-0.3.2 → cfn_check-0.9.0}/pyproject.toml +5 -4
  65. cfn_check-0.3.2/cfn_check/collection/collection.py +0 -2
  66. cfn_check-0.3.2/cfn_check/evaluation/evaluator.py +0 -85
  67. cfn_check-0.3.2/cfn_check/loader/loader.py +0 -21
  68. cfn_check-0.3.2/cfn_check.egg-info/SOURCES.txt +0 -37
  69. {cfn_check-0.3.2 → cfn_check-0.9.0}/LICENSE +0 -0
  70. {cfn_check-0.3.2 → cfn_check-0.9.0}/cfn_check/__init__.py +0 -0
  71. {cfn_check-0.3.2 → cfn_check-0.9.0}/cfn_check/cli/__init__.py +0 -0
  72. {cfn_check-0.3.2 → cfn_check-0.9.0}/cfn_check/cli/utils/__init__.py +0 -0
  73. {cfn_check-0.3.2 → cfn_check-0.9.0}/cfn_check/collection/__init__.py +0 -0
  74. {cfn_check-0.3.2 → cfn_check-0.9.0}/cfn_check/evaluation/__init__.py +0 -0
  75. {cfn_check-0.3.2 → cfn_check-0.9.0}/cfn_check/evaluation/errors.py +0 -0
  76. {cfn_check-0.3.2 → cfn_check-0.9.0}/cfn_check/evaluation/parsing/__init__.py +0 -0
  77. {cfn_check-0.3.2/cfn_check/loader → cfn_check-0.9.0/cfn_check/logging}/__init__.py +0 -0
  78. {cfn_check-0.3.2 → cfn_check-0.9.0}/cfn_check/logging/models.py +0 -0
  79. {cfn_check-0.3.2/cfn_check/logging → cfn_check-0.9.0/cfn_check/rendering/parsing}/__init__.py +0 -0
  80. /cfn_check-0.3.2/cfn_check/rules/__init__.py → /cfn_check-0.9.0/cfn_check/rendering/parsing/cloudformation_loader.py +0 -0
  81. {cfn_check-0.3.2/cfn_check/shared → cfn_check-0.9.0/cfn_check/rules}/__init__.py +0 -0
  82. {cfn_check-0.3.2/cfn_check/validation → cfn_check-0.9.0/cfn_check/shared}/__init__.py +0 -0
  83. {cfn_check-0.3.2 → cfn_check-0.9.0}/cfn_check/shared/types.py +0 -0
  84. {cfn_check-0.3.2 → cfn_check-0.9.0}/cfn_check.egg-info/dependency_links.txt +0 -0
  85. {cfn_check-0.3.2 → cfn_check-0.9.0}/cfn_check.egg-info/entry_points.txt +0 -0
  86. {cfn_check-0.3.2 → cfn_check-0.9.0}/cfn_check.egg-info/top_level.txt +0 -0
  87. {cfn_check-0.3.2 → cfn_check-0.9.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cfn-check
3
- Version: 0.3.2
3
+ Version: 0.9.0
4
4
  Summary: Validate Cloud Formation
5
5
  Author-email: Ada Lundhe <adalundhe@lundhe.audio>
6
6
  License: MIT License
@@ -27,14 +27,15 @@ License: MIT License
27
27
 
28
28
  Project-URL: Homepage, https://github.com/adalundhe/cfn-check
29
29
  Keywords: cloud-formation,testing,aws,cli
30
+ Classifier: Programming Language :: Python :: 3.11
30
31
  Classifier: Programming Language :: Python :: 3.12
31
32
  Classifier: Programming Language :: Python :: 3.13
32
33
  Classifier: Operating System :: OS Independent
33
- Requires-Python: >=3.12
34
+ Requires-Python: >=3.11
34
35
  Description-Content-Type: text/markdown
35
36
  License-File: LICENSE
36
37
  Requires-Dist: pydantic
37
- Requires-Dist: pyyaml
38
+ Requires-Dist: ruamel.yaml
38
39
  Requires-Dist: hyperlight-cocoa
39
40
  Requires-Dist: async-logging
40
41
  Dynamic: license-file
@@ -50,7 +51,7 @@ Dynamic: license-file
50
51
 
51
52
  | Package | cfn-check |
52
53
  | ----------- | ----------- |
53
- | Version | 0.3.2 |
54
+ | Version | 0.8.1 |
54
55
  | Download | https://pypi.org/project/cfn-check/ |
55
56
  | Source | https://github.com/adalundhe/cfn-check |
56
57
  | Keywords | cloud-formation, testing, aws, cli |
@@ -70,15 +71,20 @@ problems inherint to `cfn-lint` more than `cfn-guard`, primarily:
70
71
  - Inability to parse non-resource wildcards
71
72
  - Inability to validate non-resource template data
72
73
  - Inabillity to use structured models to validate input
74
+ - Poor ability to parse and render CloudFormation Refs/Functions
73
75
 
74
76
  In comparison to `cfn-guard`, `cfn-check` is pure Python, thus
75
77
  avoiding YADSL (Yet Another DSL) headaches. It also proves
76
78
  significantly more configurable/modular/hackable as a result.
79
+ `cfn-check` can resolve _some_ (not all) CloudFormation Intrinsic
80
+ Functions and Refs.
77
81
 
78
82
  CFN-Check uses a combination of simple depth-first-search tree
79
83
  parsing, friendly `cfn-lint` like query syntax, `Pydantic` models,
80
84
  and `pytest`-like assert-driven checks to make validating your
81
85
  Cloud Formation easy while offering both CLI and Python API interfaces.
86
+ CFN-Check also uses a lightning-fast AST-parser to render your templates,
87
+ allowing you to validate policy, not just a YAML document.
82
88
 
83
89
  <br/>
84
90
 
@@ -250,19 +256,20 @@ Congrats! You've just made the cloud a bit better place!
250
256
 
251
257
  # Queries, Tokens, and Syntax
252
258
 
253
- A `cfn-check` Query is a string made up of double-colon (`::`) delimited "Tokens" centered around three primary types:
259
+ A `cfn-check` Query is a string made up of period (`.`) delimited "Tokens" centered around four primary types:
254
260
 
255
261
  - <b>`Keys`</b> - `<KEY>`: String name key Tokens that perform exact matching on keys of key/value pairs in a CloudFormation document.
256
- - <b>`Patterns`</b> - `(\d+)`: Paren-enclosed regex pattern Tokens that perform pattern-based matching on keys of key/value pairs in a CloudFormation document.
262
+ - <b>`Values`</b> - `(<KEY> = <VALUE>)`: Parenthesis-enclosed `K==V` pairs that perform matching on values of key/value pairs in a CloudFormation document.
263
+ - <b>`Patterns`</b> - `<\d+>`: Arrow-enclosed regex pattern Tokens that perform pattern-based matching on keys of key/value pairs in a CloudFormation document.
257
264
  - <b>`Ranges`</b> - `[]`: Brackets enclosed Tokens that perform array selection and filtering in a CloudFormation document.
258
265
 
259
266
 
260
- In addition to `Key`, `Pattern`, and `Range` selection, you can also incorporate:
267
+ In addition to `Key`, `Value`, `Pattern`, and `Range` selection, you can also incorporate:
261
268
 
262
269
  - <b>`Bounded Ranges`</b> - `[<A>-<B>]`: Exact matches from the starting position (if specified) to the end position (if specified) of an array
263
270
  - <b>`Indicies`</b> - `[<A>]`: Exact matches the specified indicies of an array
264
271
  - <b>`Key Ranges`</b> - `[<KEY>]`: Exact matches keys of objects within an array
265
- - <b>`Pattern Ranges`</b> (`[(\d+)]`): Matches they keys of objects within an array based on the specified pattern
272
+ - <b>`Pattern Ranges`</b> (`[<\d+>]`): Matches they keys of objects within an array based on the specified pattern
266
273
  - <b>`Wildcards`</b> (`*`): Selects all values for a given object or array or returns the non-object/array value at the specified path
267
274
  - <b>`Wildcard Ranges`</b> (`[*]`): Selects all values for a given array and ensures that *only* the values of a valid array type are returned (any other type will be treated as a mismatch).
268
275
 
@@ -276,6 +283,27 @@ Resources
276
283
 
277
284
  as your query, you'll select all items within the CloudFormation document under the `Resources` key.
278
285
 
286
+
287
+ ### Working with Values
288
+
289
+ In addition to searching by keys, filtering by the values associated with those keys is the most common way you'll traverse and validate your CloudFormation template. To filter `Resource` key matches by the value of their `Type` value to only return EC2 Instances, we would specify a query like:
290
+
291
+ ```
292
+ Resources.*.(Type == AWS::EC2::Instance)
293
+ ```
294
+
295
+ You can also match multiple values by utilizing the `in`/`IN` operator:
296
+
297
+ ```
298
+ Resources.*.(Type in AWS::Lambda::Function,AWS::Serverless::Function)
299
+ ```
300
+
301
+ and providing a comma-delimited list of values.
302
+
303
+ > [!NOTE]
304
+ > Value queries do not support Nested Ranges or nested queries, but *do* support
305
+ > Wildcards and Patterns. See below for more info!
306
+
279
307
  ### Working with Patterns
280
308
 
281
309
  If an object within a CloudFormation document contains multiple similar keys you want to select, `Pattern` Tokens are your go-to solution. Consider this segment of CloudFormation:
@@ -302,7 +330,7 @@ Resources:
302
330
  We want to select <i>both</i> `SecurityGroupIngress` and `SecurityGroupEgress` to perform the same rule evaluations. Since the keys for both blocks start with `SecurityGroup`, we could write a Query using a Pattern Token like:
303
331
 
304
332
  ```
305
- Resources::SecurityGroup::Properties::(SecurityGroup)
333
+ Resources.SecurityGroup.Properties.<SecurityGroup>
306
334
  ```
307
335
 
308
336
  which would allow us to use a single rule to evaluate both:
@@ -311,7 +339,7 @@ which would allow us to use a single rule to evaluate both:
311
339
  class ValidateSecurityGroups(Collection):
312
340
 
313
341
  @Rule(
314
- "Resources::SecurityGroup::Properties::(SecurityGroup)",
342
+ "Resources.SecurityGroup.Properties.<SecurityGroup>",
315
343
  "It checks Security Groups are correctly definined",
316
344
  )
317
345
  def validate_security_groups(self, value: list[dict]):
@@ -342,7 +370,7 @@ Wildcard Tokens allow you to select all matching objects, array entries, or valu
342
370
  In fact, you've already used one! In the first example, we use a Wildcard Token in the below query:
343
371
 
344
372
  ```
345
- Resources::*::Type
373
+ Resources.*.Type
346
374
  ```
347
375
 
348
376
  To select all `Resource` objects, then further extract the `Type` field from each object. This helps us avoid copy-paste rules at the potential cost of deferring more work to individual `Rule` methods if we aren't careful and select too much!
@@ -361,7 +389,7 @@ Ranges allow you to perform sophisticated selection of objects or data within a
361
389
  Unbounded ranges allow you to select and return an array in its entirety. For example:
362
390
 
363
391
  ```
364
- Resources::SecurityGroup::Properties::SecurityGroupIngress::[]
392
+ Resources.SecurityGroup.Properties.SecurityGroupIngress.[]
365
393
  ```
366
394
 
367
395
  Would return all SecurityGroupIngress objects in the CloudFormation document as a list, allowing you to check that the array of ingresses has been both defined *and* populated.
@@ -372,7 +400,7 @@ Would return all SecurityGroupIngress objects in the CloudFormation document as
372
400
  Indexes allow you to select specific positions within an array. For example:
373
401
 
374
402
  ```
375
- Resources::SecurityGroup::Properties::SecurityGroupIngress::[0]
403
+ Resources.SecurityGroup.Properties.SecurityGroupIngress.[0]
376
404
  ```
377
405
 
378
406
  Would return the first SecurityGroupIngress objects in the document.
@@ -386,7 +414,7 @@ Bounded Ranges allow you to select subsets of indicies within an array (much lik
386
414
  As an example:
387
415
 
388
416
  ```
389
- Resources::SecurityGroup::Properties::SecurityGroupIngress::[1-3]
417
+ Resources.SecurityGroup.Properties.SecurityGroupIngress.[1-3]
390
418
  ```
391
419
 
392
420
  Would select the second and third SecurityGroupIngress objects in the document.
@@ -394,13 +422,13 @@ Would select the second and third SecurityGroupIngress objects in the document.
394
422
  Start or end positions are optional for Bounded Ranges. If a starting position is not defined, `cfn-check` will default to `0`. Likewise, if an end position is not defined, `cfn-check` will default to the end of given list. For example:
395
423
 
396
424
  ```
397
- Resources::SecurityGroup::Properties::SecurityGroupIngress::[-3]
425
+ Resources.SecurityGroup.Properties.SecurityGroupIngress.[-3]
398
426
  ```
399
427
 
400
428
  selects the first through third SecurityGroupIngress objects in the document while:
401
429
 
402
430
  ```
403
- Resources::SecurityGroup::Properties::SecurityGroupIngress::[3-]
431
+ Resources.SecurityGroup.Properties.SecurityGroupIngress.[3-]
404
432
  ```
405
433
 
406
434
  selects the remaining SecurityGroupIngress objects starting from the third.
@@ -417,7 +445,7 @@ Often times it's easier to match based upon an array's contents than by exact in
417
445
  For example:
418
446
 
419
447
  ```
420
- Resources::MyEC2Instance::Properties::ImageId::[AWSRegionArch2AMI]
448
+ Resources.MyEC2Instance.Properties.ImageId.[AWSRegionArch2AMI]
421
449
  ```
422
450
 
423
451
  returns only the EC2 ImageIds where the ImageId exactly matches `AWSRegionArch2AMI`.
@@ -428,7 +456,7 @@ returns only the EC2 ImageIds where the ImageId exactly matches `AWSRegionArch2A
428
456
  Pattern Ranges function much like Key Ranges, but utilize regex-based pattern matching for comparison. Adapting the above example:
429
457
 
430
458
  ```
431
- Resources::MyEC2Instance::Properties::ImageId::[(^AWSRegion)]
459
+ Resources.MyEC2Instance.Properties.ImageId.[<^AWSRegion>]
432
460
  ```
433
461
 
434
462
  returns only the EC2 ImageIds where the ImageId begins with `AWSRegion`. This can be helpful in checking for and enforcing naming standards, etc.
@@ -441,13 +469,13 @@ Wildcard Ranges extend the powerful functionality of Wildcard Tokens with the ad
441
469
  For example we know:
442
470
 
443
471
  ```
444
- Resources::*::Type
472
+ Resources.*.Type
445
473
  ```
446
474
 
447
475
  Selects all `Resource` objects. If we convert the Wildcard Token in the query to a Wildcard Range Token:
448
476
 
449
477
  ```
450
- Resources::*::Type
478
+ Resources.[*].Type
451
479
  ```
452
480
 
453
481
  The Rule will fail as below:
@@ -481,7 +509,7 @@ Note that the array we want is nested within another array, and we need to make
481
509
  We can accomplish this by using a Wildcard Range Token in our Query as below:
482
510
 
483
511
  ```
484
- Resources::AppendItemToListFunction::Properties::Code::ZipFile::[*]::[]
512
+ Resources.AppendItemToListFunction.Properties.Code.ZipFile.[*].[]
485
513
  ```
486
514
 
487
515
  Which allows us to then evaluate the Unbounded Range token against each array item, returning only the array we want.
@@ -498,7 +526,7 @@ You can use multiple Tokens within a Range Token by seperating each token with a
498
526
  For example:
499
527
 
500
528
  ```
501
- Resources::SecurityGroup::Properties::SecurityGroupIngress::[0, -2]
529
+ Resources.SecurityGroup.Properties.SecurityGroupIngress.[0, -2]
502
530
  ```
503
531
 
504
532
  Would select all except the last element of an array.
@@ -506,7 +534,7 @@ Would select all except the last element of an array.
506
534
  This also applies to Bounded Ranges, Key Ranges, Pattern Ranges, and Wildcard Ranges! For example:
507
535
 
508
536
  ```
509
- Resources::MyEC2Instance::Properties::ImageId::[(^AWSRegion),(^),(^Custom)]
537
+ Resources.MyEC2Instance.Properties.ImageId.[(^AWSRegion),(^),(^Custom)]
510
538
  ```
511
539
 
512
540
  will select any EC2 ImageIds that start with either `AWSRegion` or `Custom`.
@@ -530,12 +558,160 @@ ZipFile: !Join
530
558
  from our previous examples, we used the below query to select the nested array:
531
559
 
532
560
  ```
533
- Resources::AppendItemToListFunction::Properties::Code::ZipFile::[*]::[]
561
+ Resources.AppendItemToListFunction.Properties.Code.ZipFile.[*].[]
534
562
  ```
535
563
 
536
564
  With Nested Ranges, this can be shortened to:
537
565
 
538
566
  ```
539
- Resources::AppendItemToListFunction::Properties::Code::ZipFile::[[]]
567
+ Resources.AppendItemToListFunction.Properties.Code.ZipFile.[[]]
568
+ ```
540
569
 
541
570
  Which is both more concise *and* more representitave of our intention to select only the array.
571
+
572
+ # Grouping Queries
573
+
574
+ CFN-Check grouping allows you significant freedom of expression in how you write queries while *also* allowing you to more easily restrict and filter results by multiple criterion. Queries support both logical "or" and "and" statements via the `|` and `&` operators respectively. For example, consider the previous values query where we used an `in` operator:
575
+
576
+ ```
577
+ Resources.*.(Type in AWS::Lambda::Function,AWS::Serverless::Function)
578
+ ```
579
+
580
+ This could be rewritten as:
581
+ ```
582
+ Resources.*(Type == AWS::Lambda::Function | Type == AWS::Serverless::Function)
583
+ ```
584
+
585
+ A more likely scenario might be finding specifically NodeJS Lambda functions. For example:
586
+
587
+ ```
588
+ Resources.*.(Type == AWS::Lambda::Function,AWS::Serverless::Function & Properties.Runtime == <nodejs20>)
589
+ ```
590
+
591
+ Queries are "split" by `|` operator and then "grouped" by `&` operator. That means if we want a query to match one set of criterion <i>or</i> another we could write:
592
+
593
+ ```
594
+ Resources.*.(Type == AWS::Lambda::Function & Properties.Runtime == <nodejs20> | Type == AWS::Serverless::Function & Properties.Runtime == <nodejs20>)
595
+ ```
596
+
597
+ <br/>
598
+
599
+ # Using Pydantic Models
600
+
601
+ In addition to traditional `pytest`-like assert statements, `cfn-lint` can validate results returned by queries via `Pydantic` models.
602
+
603
+ For example, consider again the initial example where we validate the `Type` field of `Resource` objects.
604
+
605
+ ```python
606
+ from cfn_check import Collection, Rule
607
+
608
+
609
+ class ValidateResourceType(Collection):
610
+
611
+ @Rule(
612
+ "Resources.*.Type",
613
+ "It checks Resource.Type is correctly definined",
614
+ )
615
+ def validate_test(self, value: str):
616
+ assert value is not None, '❌ Resource Type not defined'
617
+ assert isinstance(value, str), '❌ Resource Type not a string'
618
+ ```
619
+
620
+ Rather than explicitly querying for the type field and writing assertions, we can instead define a `Pydantic` schema, then pass all `Resource` objects to that schema by specifying it as a Python type hint in our `Rule` method's signature.
621
+
622
+ ```python
623
+ from cfn_check import Collection, Rule
624
+ from pydantic import BaseModel, StrictStr
625
+
626
+ class Resource(BaseModel):
627
+ Type: StrictStr
628
+
629
+
630
+ class ValidateResourceType(Collection):
631
+
632
+ @Rule(
633
+ "Resources.*",
634
+ "It checks Resource.Type is correctly definined",
635
+ )
636
+ def validate_test(self, value: Resource):
637
+ assert value is not None
638
+ ```
639
+
640
+ By deferring type and existence assertions to `Pydantic` models, you can focus your actual assertion logic on business/security policy checks.
641
+
642
+ <br/>
643
+
644
+ # Using .query()
645
+
646
+ Some of the most challenging validations to write in CFN-Guard or CFN-Lint are those requring validation of other template information against an existing selection. For example, validating that a Lambda has a LoggingGroup attached and specified within the same template.
647
+
648
+ CFN-Check makes performing these complex assertions intuitive and painless by allowing you to execute additional querieis within a rule via the `.query()` method. For example, to perform the LoggingGroup validation above you might write:
649
+
650
+ ```python
651
+ @Rule("Resources.*.(Type in AWS::Lambda::Function,AWS::Serverless::Function)", "It validates a lambda is configured correctly")
652
+ def validate_lambda(self, lambda_resource: Lambda):
653
+ assert isinstance(lambda_resource, Lambda), "Not a valid Lambda"
654
+
655
+ log_groups = self.query(
656
+ f"Resources.{lambda_resource.Properties.LoggingConfig.LogGroup}",
657
+ transforms=[
658
+ lambda data: LoggingGroup(**data)
659
+ ]
660
+ )
661
+
662
+ assert log_groups is not None, f"No resources found for LoggingGroup {lambda_resource.Properties.LoggingConfig.LogGroup}"
663
+ assert len(log_groups) > 0, "No matching logging group found in Resources for Lambda"
664
+ ```
665
+
666
+ The `query()` method accepts the following parameters:
667
+
668
+ - `query - (str, required)`: A string CFN-Check query as used when defining Rules.
669
+ - `document - (dict/list/any, optional)`: The filepath to a CloudFormation template document. This document must have been loaded by and specified to CFN-Check either via the default CLI path param, the `-i/--import-values` optional CLI arg, or under the `input_values` config key. This will cause the query specified in the call to execute against the template specified.
670
+ - `transforms - (list[Function], optional)`: A list of functions that can be used to modify and filter returned results. In the example above, we use a single transform function to convert matches returned to a `LoggingGroup` Pydantic model instance.
671
+
672
+ # The Rendering Engine
673
+
674
+ ### Overview
675
+
676
+ In Version 0.6.X, CFN-Check introduced a rendering engine, which allows it
677
+ to parse and execute Refs and all CloudFormation intrinsic functions via
678
+ either the CloudFormation document or user-supplied values. This additional
679
+ also resulted in the:
680
+
681
+ ```bash
682
+ cfn-check render <TEMPLATE_PATH >
683
+ ```
684
+
685
+ command being added, allowing you to effectively "dry run" render your
686
+ CloudFormation templates akin to the `helm template` command for Helm.
687
+
688
+ By default, `cfn-check render` outputs to stdout, however you can easily
689
+ save rendered output to a file via the `-o/--output-file` flag. For example:
690
+
691
+ ```bash
692
+ cfn-check render template.yml -o rendered.yml
693
+ ```
694
+
695
+ The `cfn-check render` command also offers the following options:
696
+
697
+ - `-a/--attributes`: A list of <key>=<value> input `!GetAtt` attributes to use
698
+ - `-m/--mappings`: A list of <key>=<value> input `Mappings` to use
699
+ - `-p/--parameters`: A list of <key>=<value> input `Parameters` to use
700
+ - `-l/--log-level`: The log level to use
701
+
702
+ ### The Rendering Engine during Checks
703
+
704
+ By default rendering is enabled when running `cfn-check` validation. You can
705
+ disable it by supplying `no-render` to the `-F/--flags` option as below:
706
+
707
+ ```bash
708
+ cfn-check validate -F no-render -r rules.py template.yaml
709
+ ```
710
+
711
+ Disabling rendering means CFN-Check will validate your template as-is, with
712
+ no additional pre-processing and no application of user input values.
713
+
714
+ > [!WARNING]
715
+ > CloudFormation documents are <b>not</b> "plain yaml" and disabling
716
+ > rendering means any dynamically determined values will likely fail
717
+ > to pass validation, resulting in false positives for failures!