cdk-factory 0.19.6__py3-none-any.whl → 0.19.10__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.
- cdk_factory/configurations/resources/lambda_edge.py +1 -1
- cdk_factory/stack_library/cloudfront/cloudfront_stack.py +30 -26
- cdk_factory/stack_library/ecs/ecs_service_stack.py +34 -0
- cdk_factory/stack_library/lambda_edge/lambda_edge_stack.py +32 -3
- cdk_factory/version.py +1 -1
- {cdk_factory-0.19.6.dist-info → cdk_factory-0.19.10.dist-info}/METADATA +1 -1
- {cdk_factory-0.19.6.dist-info → cdk_factory-0.19.10.dist-info}/RECORD +10 -10
- {cdk_factory-0.19.6.dist-info → cdk_factory-0.19.10.dist-info}/WHEEL +0 -0
- {cdk_factory-0.19.6.dist-info → cdk_factory-0.19.10.dist-info}/entry_points.txt +0 -0
- {cdk_factory-0.19.6.dist-info → cdk_factory-0.19.10.dist-info}/licenses/LICENSE +0 -0
|
@@ -52,7 +52,7 @@ class LambdaEdgeConfig(EnhancedBaseConfig):
|
|
|
52
52
|
"""Timeout in seconds (max 5 for origin-request)"""
|
|
53
53
|
timeout = int(self._config.get("timeout", 5))
|
|
54
54
|
if timeout > 5:
|
|
55
|
-
raise ValueError("Lambda@Edge origin-request timeout cannot exceed 5 seconds")
|
|
55
|
+
raise ValueError("Lambda@Edge origin-request timeout cannot exceed 5 seconds. Value was set to {}".format(timeout))
|
|
56
56
|
return timeout
|
|
57
57
|
|
|
58
58
|
@property
|
|
@@ -145,7 +145,7 @@ class CloudFrontStack(IStack):
|
|
|
145
145
|
return
|
|
146
146
|
|
|
147
147
|
# Check if certificate ARN is provided
|
|
148
|
-
cert_arn = cert_config.get("arn")
|
|
148
|
+
cert_arn = self.resolve_ssm_value(self, cert_config.get("arn"), "CertificateARN")
|
|
149
149
|
if cert_arn:
|
|
150
150
|
self.certificate = acm.Certificate.from_certificate_arn(
|
|
151
151
|
self, "Certificate", certificate_arn=cert_arn
|
|
@@ -173,14 +173,14 @@ class CloudFrontStack(IStack):
|
|
|
173
173
|
"CloudFront certificates must be created in us-east-1"
|
|
174
174
|
)
|
|
175
175
|
return
|
|
176
|
-
|
|
176
|
+
|
|
177
177
|
# Create the certificate
|
|
178
178
|
# Get hosted zone from SSM imports
|
|
179
179
|
hosted_zone_id = cert_config.get("hosted_zone_id")
|
|
180
180
|
hosted_zone = route53.HostedZone.from_hosted_zone_id(
|
|
181
181
|
self, "HostedZone", hosted_zone_id
|
|
182
182
|
)
|
|
183
|
-
|
|
183
|
+
|
|
184
184
|
self.certificate = acm.Certificate(
|
|
185
185
|
self,
|
|
186
186
|
"Certificate",
|
|
@@ -223,27 +223,29 @@ class CloudFrontStack(IStack):
|
|
|
223
223
|
|
|
224
224
|
def _create_custom_origin(self, config: Dict[str, Any]) -> cloudfront.IOrigin:
|
|
225
225
|
"""Create custom origin (ALB, API Gateway, etc.)"""
|
|
226
|
-
domain_name =
|
|
226
|
+
domain_name = self.resolve_ssm_value(
|
|
227
|
+
self, config.get("domain_name"), config.get("domain_name")
|
|
228
|
+
)
|
|
227
229
|
origin_id = config.get("id")
|
|
228
230
|
|
|
229
231
|
if not domain_name:
|
|
230
232
|
raise ValueError("domain_name is required for custom origin")
|
|
231
233
|
|
|
232
|
-
# Check if domain name is a placeholder from ssm_imports
|
|
233
|
-
if domain_name.startswith("{{") and domain_name.endswith("}}"):
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
# Legacy support: Check if domain name is an SSM parameter reference
|
|
242
|
-
elif domain_name.startswith("{{ssm:") and domain_name.endswith("}}"):
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
234
|
+
# # Check if domain name is a placeholder from ssm_imports
|
|
235
|
+
# if domain_name.startswith("{{") and domain_name.endswith("}}"):
|
|
236
|
+
# placeholder_key = domain_name[2:-2] # Remove {{ and }}
|
|
237
|
+
# if placeholder_key in self.ssm_imported_values:
|
|
238
|
+
# domain_name = self.ssm_imported_values[placeholder_key]
|
|
239
|
+
# logger.info(f"Resolved domain from SSM import: {placeholder_key}")
|
|
240
|
+
# else:
|
|
241
|
+
# logger.warning(f"Placeholder {domain_name} not found in SSM imports")
|
|
242
|
+
|
|
243
|
+
# # Legacy support: Check if domain name is an SSM parameter reference
|
|
244
|
+
# elif domain_name.startswith("{{ssm:") and domain_name.endswith("}}"):
|
|
245
|
+
# # Extract SSM parameter name
|
|
246
|
+
# ssm_param = domain_name[6:-2] # Remove {{ssm: and }}
|
|
247
|
+
# domain_name = ssm.StringParameter.value_from_lookup(self, ssm_param)
|
|
248
|
+
# logger.info(f"Resolved domain from SSM lookup {ssm_param}: {domain_name}")
|
|
247
249
|
|
|
248
250
|
# Build custom headers (e.g., X-Origin-Secret)
|
|
249
251
|
custom_headers = {}
|
|
@@ -297,20 +299,22 @@ class CloudFrontStack(IStack):
|
|
|
297
299
|
|
|
298
300
|
def _create_s3_origin(self, config: Dict[str, Any]) -> cloudfront.IOrigin:
|
|
299
301
|
"""Create S3 origin"""
|
|
300
|
-
bucket_name =
|
|
301
|
-
|
|
302
|
+
bucket_name = self.resolve_ssm_value(
|
|
303
|
+
self, config.get("bucket_name"), config.get("bucket_name")
|
|
304
|
+
)
|
|
305
|
+
|
|
302
306
|
origin_path = config.get("origin_path", "")
|
|
303
|
-
|
|
307
|
+
|
|
304
308
|
if not bucket_name:
|
|
305
309
|
raise ValueError("S3 origin requires 'bucket_name' configuration")
|
|
306
|
-
|
|
310
|
+
|
|
307
311
|
# For S3 origins, we need to import the bucket by name
|
|
308
312
|
bucket = s3.Bucket.from_bucket_name(
|
|
309
313
|
self,
|
|
310
314
|
id=f"S3OriginBucket-{config.get('id', 'unknown')}",
|
|
311
|
-
bucket_name=bucket_name
|
|
315
|
+
bucket_name=bucket_name,
|
|
312
316
|
)
|
|
313
|
-
|
|
317
|
+
|
|
314
318
|
# Create S3 origin with OAC (Origin Access Control) for security
|
|
315
319
|
origin = origins.S3BucketOrigin.with_origin_access_control(
|
|
316
320
|
bucket,
|
|
@@ -320,7 +324,7 @@ class CloudFrontStack(IStack):
|
|
|
320
324
|
cloudfront.AccessLevel.LIST,
|
|
321
325
|
],
|
|
322
326
|
)
|
|
323
|
-
|
|
327
|
+
|
|
324
328
|
return origin
|
|
325
329
|
|
|
326
330
|
def _create_distribution(self) -> None:
|
|
@@ -226,6 +226,9 @@ class EcsServiceStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
226
226
|
"CloudWatchAgentServerPolicy"
|
|
227
227
|
)
|
|
228
228
|
)
|
|
229
|
+
|
|
230
|
+
# add any custom policies
|
|
231
|
+
self._add_custom_task_policies(task_role)
|
|
229
232
|
|
|
230
233
|
# Create task definition based on launch type
|
|
231
234
|
if self.ecs_config.launch_type == "EC2":
|
|
@@ -257,6 +260,37 @@ class EcsServiceStack(IStack, VPCProviderMixin, StandardizedSsmMixin):
|
|
|
257
260
|
# Add containers
|
|
258
261
|
self._add_containers_to_task()
|
|
259
262
|
|
|
263
|
+
def _add_custom_task_policies(self, task_role: iam.Role) -> None:
|
|
264
|
+
"""
|
|
265
|
+
Add custom task policies to the task definition.
|
|
266
|
+
"""
|
|
267
|
+
for policy in self.ecs_config.task_definition.get("policies", []):
|
|
268
|
+
|
|
269
|
+
effect = policy.get("effect", "Allow")
|
|
270
|
+
action = policy.get("action", None)
|
|
271
|
+
actions = policy.get("actions", [])
|
|
272
|
+
if action:
|
|
273
|
+
actions.append(action)
|
|
274
|
+
resources = policy.get("resources", [])
|
|
275
|
+
resource = policy.get("resource", None)
|
|
276
|
+
if resource:
|
|
277
|
+
resources.append(resource)
|
|
278
|
+
|
|
279
|
+
if effect == "Allow" and actions:
|
|
280
|
+
effect = iam.Effect.ALLOW
|
|
281
|
+
if effect == "Deny" and actions:
|
|
282
|
+
effect = iam.Effect.DENY
|
|
283
|
+
|
|
284
|
+
sid = policy.get("sid", None)
|
|
285
|
+
task_role.add_to_policy(
|
|
286
|
+
iam.PolicyStatement(
|
|
287
|
+
effect=effect,
|
|
288
|
+
actions=actions,
|
|
289
|
+
resources=resources,
|
|
290
|
+
sid=sid,
|
|
291
|
+
)
|
|
292
|
+
)
|
|
293
|
+
|
|
260
294
|
def _add_volumes_to_task(self) -> None:
|
|
261
295
|
"""
|
|
262
296
|
Add volumes to the task definition.
|
|
@@ -53,6 +53,8 @@ class LambdaEdgeStack(IStack, StandardizedSsmMixin):
|
|
|
53
53
|
self.workload: Optional[WorkloadConfig] = None
|
|
54
54
|
self.function: Optional[_lambda.Function] = None
|
|
55
55
|
self.function_version: Optional[_lambda.Version] = None
|
|
56
|
+
# Cache for resolved environment variables to prevent duplicate construct creation
|
|
57
|
+
self._resolved_env_cache: Optional[Dict[str, str]] = None
|
|
56
58
|
|
|
57
59
|
def build(
|
|
58
60
|
self,
|
|
@@ -101,12 +103,32 @@ class LambdaEdgeStack(IStack, StandardizedSsmMixin):
|
|
|
101
103
|
# Add outputs
|
|
102
104
|
self._add_outputs(function_name)
|
|
103
105
|
|
|
106
|
+
def _sanitize_construct_name(self, name: str) -> str:
|
|
107
|
+
"""
|
|
108
|
+
Create a deterministic, valid CDK construct name from any string.
|
|
109
|
+
Replaces non-alphanumeric characters with dashes and limits length.
|
|
110
|
+
"""
|
|
111
|
+
# Replace non-alphanumeric characters with dashes
|
|
112
|
+
sanitized = ''.join(c if c.isalnum() else '-' for c in name)
|
|
113
|
+
# Remove consecutive dashes
|
|
114
|
+
while '--' in sanitized:
|
|
115
|
+
sanitized = sanitized.replace('--', '-')
|
|
116
|
+
# Remove leading/trailing dashes
|
|
117
|
+
sanitized = sanitized.strip('-')
|
|
118
|
+
# Limit to 255 characters (CDK limit)
|
|
119
|
+
return sanitized[:255]
|
|
120
|
+
|
|
104
121
|
def _resolve_environment_variables(self) -> Dict[str, str]:
|
|
105
122
|
"""
|
|
106
123
|
Resolve environment variables, including SSM parameter references.
|
|
107
124
|
Supports {{ssm:parameter-path}} syntax for dynamic SSM lookups.
|
|
108
125
|
Uses CDK tokens that resolve at deployment time, not synthesis time.
|
|
126
|
+
Caches results to prevent duplicate construct creation.
|
|
109
127
|
"""
|
|
128
|
+
# Return cached result if available
|
|
129
|
+
if self._resolved_env_cache is not None:
|
|
130
|
+
return self._resolved_env_cache
|
|
131
|
+
|
|
110
132
|
resolved_env = {}
|
|
111
133
|
|
|
112
134
|
for key, value in self.edge_config.environment.items():
|
|
@@ -115,18 +137,23 @@ class LambdaEdgeStack(IStack, StandardizedSsmMixin):
|
|
|
115
137
|
# Extract SSM parameter path
|
|
116
138
|
ssm_param_path = value[6:-2] # Remove {{ssm: and }}
|
|
117
139
|
|
|
140
|
+
# Create deterministic construct name from parameter path
|
|
141
|
+
construct_name = self._sanitize_construct_name(f"env-{key}-{ssm_param_path}")
|
|
142
|
+
|
|
118
143
|
# Import SSM parameter - this creates a token that resolves at deployment time
|
|
119
144
|
param = ssm.StringParameter.from_string_parameter_name(
|
|
120
145
|
self,
|
|
121
|
-
|
|
146
|
+
construct_name,
|
|
122
147
|
ssm_param_path
|
|
123
148
|
)
|
|
124
149
|
resolved_value = param.string_value
|
|
125
|
-
logger.info(f"Resolved environment variable {key} from SSM {ssm_param_path}")
|
|
150
|
+
logger.info(f"Resolved environment variable {key} from SSM {ssm_param_path} as {construct_name}")
|
|
126
151
|
resolved_env[key] = resolved_value
|
|
127
152
|
else:
|
|
128
153
|
resolved_env[key] = value
|
|
129
154
|
|
|
155
|
+
# Cache the result
|
|
156
|
+
self._resolved_env_cache = resolved_env
|
|
130
157
|
return resolved_env
|
|
131
158
|
|
|
132
159
|
def _create_lambda_function(self, function_name: str) -> None:
|
|
@@ -185,10 +212,12 @@ class LambdaEdgeStack(IStack, StandardizedSsmMixin):
|
|
|
185
212
|
# Create runtime configuration file for Lambda@Edge
|
|
186
213
|
# Since Lambda@Edge doesn't support environment variables, we bundle a config file
|
|
187
214
|
# Use the full function_name (e.g., "tech-talk-dev-ip-gate") not just the base name
|
|
215
|
+
resolved_env = self._resolve_environment_variables()
|
|
188
216
|
runtime_config = {
|
|
189
217
|
'environment': self.deployment.environment,
|
|
190
218
|
'function_name': function_name,
|
|
191
|
-
'region': self.deployment.region
|
|
219
|
+
'region': self.deployment.region,
|
|
220
|
+
'environment_variables': resolved_env # Add actual environment variables
|
|
192
221
|
}
|
|
193
222
|
|
|
194
223
|
runtime_config_path = temp_code_dir / 'runtime_config.json'
|
cdk_factory/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "0.19.
|
|
1
|
+
__version__ = "0.19.10"
|
|
@@ -2,7 +2,7 @@ cdk_factory/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
2
2
|
cdk_factory/app.py,sha256=RnX0-pwdTAPAdKJK_j13Zl8anf9zYKBwboR0KA8K8xM,10346
|
|
3
3
|
cdk_factory/cdk.json,sha256=SKZKhJ2PBpFH78j-F8S3VDYW-lf76--Q2I3ON-ZIQfw,3106
|
|
4
4
|
cdk_factory/cli.py,sha256=FGbCTS5dYCNsfp-etshzvFlGDCjC28r6rtzYbe7KoHI,6407
|
|
5
|
-
cdk_factory/version.py,sha256=
|
|
5
|
+
cdk_factory/version.py,sha256=gIAvq9SB6YStPs9R4fOTA5ElNAzsxGid7xxii31j-fI,24
|
|
6
6
|
cdk_factory/builds/README.md,sha256=9BBWd7bXpyKdMU_g2UljhQwrC9i5O_Tvkb6oPvndoZk,90
|
|
7
7
|
cdk_factory/commands/command_loader.py,sha256=QbLquuP_AdxtlxlDy-2IWCQ6D-7qa58aphnDPtp_uTs,3744
|
|
8
8
|
cdk_factory/configurations/base_config.py,sha256=eJ3Pl3GWk1jVr_bYQaaWlw4_-ZiFGaiXllI_fOOX1i0,9323
|
|
@@ -34,7 +34,7 @@ cdk_factory/configurations/resources/ecr.py,sha256=iJEtKqBT7vQU0LU4urIglraIR7cPZ
|
|
|
34
34
|
cdk_factory/configurations/resources/ecs_cluster.py,sha256=mQYJu7SUPDl5E4dMR6HCPFoWvFA3RGIb0iMNn-K7LX8,3635
|
|
35
35
|
cdk_factory/configurations/resources/ecs_service.py,sha256=bOWjVECd6Kbc5NGGSnDaopnKrjRsUfmaZ6-qrsmTs3Q,6468
|
|
36
36
|
cdk_factory/configurations/resources/exisiting.py,sha256=EVOLnkB-DGfTlmDgyQ5DD5k2zYfpFxqI3gugDR7mifI,478
|
|
37
|
-
cdk_factory/configurations/resources/lambda_edge.py,sha256=
|
|
37
|
+
cdk_factory/configurations/resources/lambda_edge.py,sha256=1tzxNPIsUSbwrdBHWrmqg08S629U3IGHNaNjEaLO_r8,3447
|
|
38
38
|
cdk_factory/configurations/resources/lambda_function.py,sha256=VENZ9-ABJ5mjcN8J8wdLH4KHDYr1kWO0iFDH0B2mJXA,14659
|
|
39
39
|
cdk_factory/configurations/resources/lambda_layers.py,sha256=gVeP_-LC3Eq0lkPaG_JfFUwboM5evRPr99SfKj53m7A,633
|
|
40
40
|
cdk_factory/configurations/resources/lambda_triggers.py,sha256=MD7cdMNKEulNBhtMLIFnWJuJ5R-yyIqa0LHUgbSQerA,834
|
|
@@ -91,7 +91,7 @@ cdk_factory/stack_library/aws_lambdas/lambda_stack.py,sha256=SFbBPvvCopbyiuYtq-O
|
|
|
91
91
|
cdk_factory/stack_library/buckets/README.md,sha256=XkK3UNVtRLE7NtUvbhCOBBYUYi8hlrrSaI1s3GJVrqI,78
|
|
92
92
|
cdk_factory/stack_library/buckets/bucket_stack.py,sha256=SLoZqSffAqmeBBEVUQg54D_8Ad5UKdkjEAmKAVgAqQo,1778
|
|
93
93
|
cdk_factory/stack_library/cloudfront/__init__.py,sha256=Zfx50q4xIJ4ZEoVIzUBDTKbRE9DKDM6iyVIFhtQXvww,153
|
|
94
|
-
cdk_factory/stack_library/cloudfront/cloudfront_stack.py,sha256=
|
|
94
|
+
cdk_factory/stack_library/cloudfront/cloudfront_stack.py,sha256=7cYPqoQyiXH6r3j9jp9oLXv1ZDixeCYPAXJtTOmagPc,32309
|
|
95
95
|
cdk_factory/stack_library/code_artifact/code_artifact_stack.py,sha256=o86cmC_ZV82z-K7DoAR0u1nAieoTi-vxRF01tyJn-9M,5297
|
|
96
96
|
cdk_factory/stack_library/cognito/cognito_stack.py,sha256=3tjKCNcIwXZn7fd4EDQdY6H9m6CnZohI4uTQ4TpacRQ,25327
|
|
97
97
|
cdk_factory/stack_library/dynamodb/dynamodb_stack.py,sha256=-_Ij1zXIxUuZIWgdevam_1vD3LEJ6pFs9U0hmw0KwIw,6743
|
|
@@ -99,9 +99,9 @@ cdk_factory/stack_library/ecr/README.md,sha256=xw2wPx9WN03Y4BBwqvbi9lAFGNyaD1FUN
|
|
|
99
99
|
cdk_factory/stack_library/ecr/ecr_stack.py,sha256=KLbd5WN5-ZiojsS5wJ4PX-tIL0cCylCSvXjO6sVrgWY,2102
|
|
100
100
|
cdk_factory/stack_library/ecs/__init__.py,sha256=o5vGDtD_h-gVXb3-Ysr8xUNpEcMsnmMVgZv2Pupcdow,219
|
|
101
101
|
cdk_factory/stack_library/ecs/ecs_cluster_stack.py,sha256=sAPTLU5CAwMoLTW_pNy_cd0OtVkfDR7IxxsSq5AE0yo,12091
|
|
102
|
-
cdk_factory/stack_library/ecs/ecs_service_stack.py,sha256=
|
|
102
|
+
cdk_factory/stack_library/ecs/ecs_service_stack.py,sha256=KB4YCIsMm5JIGM9Bm-bKcr3eX5xXFgnoA7jST_ekK44,28209
|
|
103
103
|
cdk_factory/stack_library/lambda_edge/__init__.py,sha256=ByBJ_CWdc4UtTmFBZH-6pzBMNkjkdtE65AmnB0Fs6lM,156
|
|
104
|
-
cdk_factory/stack_library/lambda_edge/lambda_edge_stack.py,sha256=
|
|
104
|
+
cdk_factory/stack_library/lambda_edge/lambda_edge_stack.py,sha256=7owFVRijjtyAAgwRWfVophJlwi9ATDon9ekkJdSQTNw,17050
|
|
105
105
|
cdk_factory/stack_library/load_balancer/__init__.py,sha256=wZpKw2OecLJGdF5mPayCYAEhu2H3c2gJFFIxwXftGDU,52
|
|
106
106
|
cdk_factory/stack_library/load_balancer/load_balancer_stack.py,sha256=ApW5q3SAvSJtiK0RInNljmubqXqKZU5QBAaUoeIW-pM,28287
|
|
107
107
|
cdk_factory/stack_library/monitoring/__init__.py,sha256=k1G_KDx47Aw0UugaL99PN_TKlyLK4nkJVApCaAK7GJg,153
|
|
@@ -136,8 +136,8 @@ cdk_factory/utilities/os_execute.py,sha256=5Op0LY_8Y-pUm04y1k8MTpNrmQvcLmQHPQITE
|
|
|
136
136
|
cdk_factory/utils/api_gateway_utilities.py,sha256=If7Xu5s_UxmuV-kL3JkXxPLBdSVUKoLtohm0IUFoiV8,4378
|
|
137
137
|
cdk_factory/validation/config_validator.py,sha256=Pb0TkLiPFzUplBOgMorhRCVm08vEzZhRU5xXCDTa5CA,17602
|
|
138
138
|
cdk_factory/workload/workload_factory.py,sha256=yDI3cRhVI5ELNDcJPLpk9UY54Uind1xQoV3spzT4z7E,6068
|
|
139
|
-
cdk_factory-0.19.
|
|
140
|
-
cdk_factory-0.19.
|
|
141
|
-
cdk_factory-0.19.
|
|
142
|
-
cdk_factory-0.19.
|
|
143
|
-
cdk_factory-0.19.
|
|
139
|
+
cdk_factory-0.19.10.dist-info/METADATA,sha256=DBpgjyIwcNUX78kwx-jdTlkL9okzQz57iaG23jbF6jU,2452
|
|
140
|
+
cdk_factory-0.19.10.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
141
|
+
cdk_factory-0.19.10.dist-info/entry_points.txt,sha256=S1DPe0ORcdiwEALMN_WIo3UQrW_g4YdQCLEsc_b0Swg,53
|
|
142
|
+
cdk_factory-0.19.10.dist-info/licenses/LICENSE,sha256=NOtdOeLwg2il_XBJdXUPFPX8JlV4dqTdDGAd2-khxT8,1066
|
|
143
|
+
cdk_factory-0.19.10.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|