cfn-check 0.2.2__py3-none-any.whl → 0.3.0__py3-none-any.whl

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.

Potentially problematic release.


This version of cfn-check might be problematic. Click here for more details.

@@ -1,137 +0,0 @@
1
- import re
2
- from collections import deque
3
- from typing import Deque
4
- from cfn_check.shared.types import (
5
- Data,
6
- Items,
7
- YamlList,
8
- YamlObject,
9
- YamlValueBase,
10
- )
11
-
12
-
13
- numbers_pattern = re.compile(r'\d+')
14
-
15
- def search(
16
- resources: YamlObject,
17
- path: str,
18
- ):
19
- items: Items = deque()
20
- items.append(resources)
21
-
22
- segments = path.split("::")[::-1]
23
- # Queries can be multi-segment,
24
- # so we effectively perform per-segment
25
- # repeated DFS searches, returning the matches
26
- # for each segment
27
-
28
- composite_keys: list[str] = []
29
-
30
- while len(segments):
31
- query = segments.pop()
32
- items, keys = search_with_query(items, query)
33
-
34
- if len(composite_keys) == 0:
35
- composite_keys.extend(keys)
36
-
37
- else:
38
- updated_keys: list[str] = []
39
- for composite_key in composite_keys:
40
- while len(keys):
41
- key = keys.pop()
42
-
43
- updated_keys.append(f'{composite_key}.{key}')
44
-
45
- composite_keys = updated_keys
46
-
47
- results: list[tuple[str, Data]] = []
48
- for idx, item in enumerate(list(items)):
49
- results.append((
50
- composite_keys[idx],
51
- item,
52
- ))
53
-
54
- return results
55
-
56
-
57
- def parse_list_query(query: str):
58
-
59
- queries = query.strip('[]').split(',')
60
-
61
- if len(queries) < 1:
62
- return None
63
-
64
- indexes = []
65
- for query in queries:
66
-
67
- if match := numbers_pattern.match(query):
68
- indexes.append(
69
- int(match.group(0))
70
- )
71
-
72
- return indexes
73
-
74
-
75
- def parse_list_matches(
76
- query: str,
77
- node: YamlList,
78
- ):
79
- if indexes := parse_list_query(query):
80
- return [
81
- item
82
- for idx, item in enumerate(node)
83
- if idx in indexes
84
- ]
85
-
86
- return [
87
- str(idx) for idx in indexes
88
- ], node
89
-
90
-
91
- def search_with_query(
92
- items: Items,
93
- query: str,
94
- ) -> tuple[Items, Deque[str]]:
95
-
96
- found: Items = deque()
97
-
98
- keys: Deque[str] = deque()
99
-
100
- while len(items):
101
- node = items.pop()
102
-
103
- key: (
104
- str | None
105
- ) = None
106
- value: (
107
- YamlValueBase | YamlList | YamlObject | None
108
- ) = None
109
-
110
- if isinstance(node, dict):
111
- items.extend(node.items())
112
-
113
- elif query.startswith('[') and query.startswith(']'):
114
- indexes, matched = parse_list_matches(query, node)
115
- keys.extend(indexes)
116
- found.extend(matched)
117
-
118
- elif isinstance(node, list):
119
- items.extend(node)
120
-
121
- elif isinstance(node, tuple):
122
- key, value = node
123
-
124
- else:
125
- # If we encounter just
126
- # a raw YAML int/bool/etc
127
- # then we should just
128
- # skip to the next iteration
129
- continue
130
-
131
- if (
132
- key == query or query == "*"
133
- ) and value:
134
- keys.append(key)
135
- found.append(value)
136
-
137
- return found, keys
@@ -1,247 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: cfn-check
3
- Version: 0.2.2
4
- Summary: Validate Cloud Formation
5
- Author-email: Ada Lundhe <adalundhe@lundhe.audio>
6
- License: MIT License
7
-
8
- Copyright (c) 2025 Ada Lündhé
9
-
10
- Permission is hereby granted, free of charge, to any person obtaining a copy
11
- of this software and associated documentation files (the "Software"), to deal
12
- in the Software without restriction, including without limitation the rights
13
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
- copies of the Software, and to permit persons to whom the Software is
15
- furnished to do so, subject to the following conditions:
16
-
17
- The above copyright notice and this permission notice shall be included in all
18
- copies or substantial portions of the Software.
19
-
20
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
- SOFTWARE.
27
-
28
- Project-URL: Homepage, https://github.com/adalundhe/cfn-check
29
- Keywords: cloud-formation,testing,aws,cli
30
- Classifier: Programming Language :: Python :: 3.12
31
- Classifier: Programming Language :: Python :: 3.13
32
- Classifier: Operating System :: OS Independent
33
- Requires-Python: >=3.12
34
- Description-Content-Type: text/markdown
35
- License-File: LICENSE
36
- Requires-Dist: pydantic
37
- Requires-Dist: pyyaml
38
- Requires-Dist: hyperlight-cocoa
39
- Requires-Dist: async-logging
40
- Dynamic: license-file
41
-
42
- # <b>CFN-Check</b>
43
- <b>A tool for checking CloudFormation</b>
44
-
45
- [![PyPI version](https://img.shields.io/pypi/v/cfn-check?color=blue)](https://pypi.org/project/cfn-check/)
46
- [![License](https://img.shields.io/github/license/adalundhe/cfn-check)](https://github.com/adalundhe/cfn-check/blob/main/LICENSE)
47
- [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](https://github.com/adalundhe/cfn-check/blob/main/CODE_OF_CONDUCT.md)
48
- [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/cfn-check?color=red)](https://pypi.org/project/cfn-check/)
49
-
50
-
51
- | Package | cfn-check |
52
- | ----------- | ----------- |
53
- | Version | 0.2.0 |
54
- | Download | https://pypi.org/project/cfn-check/ |
55
- | Source | https://github.com/adalundhe/cfn-check |
56
- | Keywords | cloud-formation, testing, aws, cli |
57
-
58
-
59
- CFN-Check is a small, fast, friendly tool for validating AWS CloudFormation YAML templates. It is code-driven, with
60
- rules written as simple, `Rule` decorator wrapped python class methods for `Collection`-inheriting classes.
61
-
62
- <br/>
63
-
64
- # Why CFN-Check?
65
-
66
- AWS has its own tools for validating Cloud Formation - `cfn-lint` and `cfn-guard`. `cfn-check` aims to solve
67
- problems inherint to `cfn-lint` more than `cfn-guard`, primarily:
68
-
69
- - Confusing, unclear syntax around rules configuration
70
- - Inability to parse non-resource wildcards
71
- - Inability to validate non-resource template data
72
- - Inabillity to use structured models to validate input
73
-
74
- In comparison to `cfn-guard`, `cfn-check` is pure Python, thus
75
- avoiding YADSL (Yet Another DSL) headaches. It also proves
76
- significantly more configurable/modular/hackable as a result.
77
-
78
- CFN-Check uses a combination of simple depth-first-search tree
79
- parsing, friendly `cfn-lint` like query syntax, `Pydantic` models,
80
- and `pytest`-like assert-driven checks to make validating your
81
- Cloud Formation easy while offering both CLI and Python API interfaces.
82
-
83
- <br/>
84
-
85
- # Getting Started
86
-
87
- `cfn-check` requires:
88
-
89
- - `Python 3.12`
90
- - Any number of valid CloudFormation templates or a path to said templates.
91
- - A `.py` file containing at least one `Collection` class with at least one valid `@Rule()` decorated method
92
-
93
- To get started (we recommend using `uv`), run:
94
-
95
- ```bash
96
- uv venv
97
- source .venv/bin/activate
98
-
99
- uv pip install cfn-check
100
-
101
- touch rules.py
102
- touch template.yaml
103
- ```
104
-
105
- Next open the `rules.py` file and create a basic Python class
106
- as below.
107
-
108
- ```python
109
- from cfn_check import Collection, Rule
110
-
111
-
112
- class ValidateResourceType(Collection):
113
-
114
- @Rule(
115
- "Resources::*::Type",
116
- "It checks Resource::Type is correctly definined",
117
- )
118
- def validate_test(self, value: str):
119
- assert value is not None, '❌ Resource Type not defined'
120
- assert isinstance(value, str), '❌ Resource Type not a string'
121
- ```
122
-
123
- This provides us a basic rule set that validates that the `Type` field of our CloudFormation template(s) exists and is the correct data type.
124
-
125
- > [!NOTE]
126
- > Don't worry about adding an `__init__()` method to this class!
127
-
128
- Next open the `template.yaml` file and paste the following CloudFormation:
129
-
130
- ```yaml
131
- AWSTemplateFormatVersion: '2010-09-09'
132
- Parameters:
133
- ExistingSecurityGroups:
134
- Type: List<AWS::EC2::SecurityGroup::Id>
135
- ExistingVPC:
136
- Type: AWS::EC2::VPC::Id
137
- Description: The VPC ID that includes the security groups in the ExistingSecurityGroups parameter.
138
- InstanceType:
139
- Type: String
140
- Default: t2.micro
141
- AllowedValues:
142
- - t2.micro
143
- - m1.small
144
- Mappings:
145
- AWSInstanceType2Arch:
146
- t2.micro:
147
- Arch: HVM64
148
- m1.small:
149
- Arch: HVM64
150
- AWSRegionArch2AMI:
151
- us-east-1:
152
- HVM64: ami-0ff8a91507f77f867
153
- HVMG2: ami-0a584ac55a7631c0c
154
- Resources:
155
- SecurityGroup:
156
- Type: AWS::EC2::SecurityGroup
157
- Properties:
158
- GroupDescription: Allow HTTP traffic to the host
159
- VpcId: !Ref ExistingVPC
160
- SecurityGroupIngress:
161
- - IpProtocol: tcp
162
- FromPort: 80
163
- ToPort: 80
164
- CidrIp: 0.0.0.0/0
165
- SecurityGroupEgress:
166
- - IpProtocol: tcp
167
- FromPort: 80
168
- ToPort: 80
169
- CidrIp: 0.0.0.0/0
170
- AllSecurityGroups:
171
- Type: Custom::Split
172
- Properties:
173
- ServiceToken: !GetAtt AppendItemToListFunction.Arn
174
- List: !Ref ExistingSecurityGroups
175
- AppendedItem: !Ref SecurityGroup
176
- AppendItemToListFunction:
177
- Type: AWS::Lambda::Function
178
- Properties:
179
- Handler: index.handler
180
- Role: !GetAtt LambdaExecutionRole.Arn
181
- Code:
182
- ZipFile: !Join
183
- - ''
184
- - - var response = require('cfn-response');
185
- - exports.handler = function(event, context) {
186
- - ' var responseData = {Value: event.ResourceProperties.List};'
187
- - ' responseData.Value.push(event.ResourceProperties.AppendedItem);'
188
- - ' response.send(event, context, response.SUCCESS, responseData);'
189
- - '};'
190
- Runtime: nodejs20.x
191
- MyEC2Instance:
192
- Type: AWS::EC2::Instance
193
- Properties:
194
- ImageId: !FindInMap
195
- - AWSRegionArch2AMI
196
- - !Ref AWS::Region
197
- - !FindInMap
198
- - AWSInstanceType2Arch
199
- - !Ref InstanceType
200
- - Arch
201
- SecurityGroupIds: !GetAtt AllSecurityGroups.Value
202
- InstanceType: !Ref InstanceType
203
- LambdaExecutionRole:
204
- Type: AWS::IAM::Role
205
- Properties:
206
- AssumeRolePolicyDocument:
207
- Version: '2012-10-17'
208
- Statement:
209
- - Effect: Allow
210
- Principal:
211
- Service:
212
- - lambda.amazonaws.com
213
- Action:
214
- - sts:AssumeRole
215
- Path: /
216
- Policies:
217
- - PolicyName: root
218
- PolicyDocument:
219
- Version: '2012-10-17'
220
- Statement:
221
- - Effect: Allow
222
- Action:
223
- - logs:*
224
- Resource: arn:aws:logs:*:*:*
225
- Outputs:
226
- AllSecurityGroups:
227
- Description: Security Groups that are associated with the EC2 instance
228
- Value: !Join
229
- - ', '
230
- - !GetAtt AllSecurityGroups.Value
231
- ```
232
-
233
- This represents a basic configuration for an AWS Lambda function.
234
-
235
- Finally, run:
236
-
237
- ```bash
238
- cfn-check validate -r rules.py template.yaml
239
- ```
240
-
241
- which outputs:
242
-
243
- ```
244
- 2025-09-17T01:46:41.542078+00:00 - INFO - 19783474 - /Users/adalundhe/Documents/adalundhe/cfn-check/cfn_check/cli/validate.py:validate.80 - ✅ 1 validations met for 1 templates
245
- ```
246
-
247
- Congrats! You've just made the cloud a bit better place!