cdk-factory 0.19.13__py3-none-any.whl → 0.19.19__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.
@@ -0,0 +1,226 @@
1
+ # Lambda@Edge Log Retention - Implementation Plan
2
+
3
+ ## 🚨 Current Status: DISABLED
4
+
5
+ Lambda@Edge log retention configuration has been **disabled** because edge log groups are created on-demand when the function is invoked at edge locations, not during CloudFormation deployment.
6
+
7
+ ## 🔍 Problem Analysis
8
+
9
+ ### Why Deployment-Time Configuration Fails
10
+ 1. **On-Demand Creation**: Lambda@Edge log groups are created only when the function is actually invoked at edge locations
11
+ 2. **Timing Issue**: CloudFormation deployment happens before any edge invocations occur
12
+ 3. **Error**: `The specified log group does not exist` when trying to set retention policies
13
+
14
+ ### Log Group Naming Pattern
15
+ ```
16
+ Pattern: /aws/lambda/{edge-region}.{function-name}
17
+ Example: /aws/lambda/eu-central-1.trav-talks-blue-green-edge-function
18
+ Location: All edge log groups are created in us-east-1
19
+ ```
20
+
21
+ ## 💡 Proposed Solutions
22
+
23
+ ### Solution 1: EventBridge + Lambda (Recommended)
24
+ ```yaml
25
+ # EventBridge rule to detect log group creation
26
+ EventPattern:
27
+ source: ["aws.logs"]
28
+ detail-type: ["AWS API Call via CloudTrail"]
29
+ detail:
30
+ eventSource: ["logs.amazonaws.com"]
31
+ eventName: ["CreateLogGroup"]
32
+ requestParameters:
33
+ logGroupName: ["/aws/lambda/*.edge-function"]
34
+ ```
35
+
36
+ **Implementation:**
37
+ 1. Create EventBridge rule that triggers on log group creation
38
+ 2. Lambda function receives event and sets retention policy
39
+ 3. Automatic handling of new edge log groups
40
+
41
+ **Pros:**
42
+ - Automatic and real-time
43
+ - No manual intervention required
44
+ - Handles all edge regions
45
+
46
+ **Cons:**
47
+ - Additional Lambda function to maintain
48
+ - Requires CloudTrail enabled for CloudWatch Logs
49
+
50
+ ### Solution 2: Periodic Lambda Function
51
+ ```python
52
+ def lambda_handler(event, context):
53
+ # Scan for edge log groups
54
+ log_groups = logs.describe_log_groups(
55
+ logGroupNamePrefix='/aws/lambda/eu-central-1.trav-talks-blue-green-edge-function'
56
+ )
57
+
58
+ # Apply retention policy
59
+ for log_group in log_groups['logGroups']:
60
+ logs.put_retention_policy(
61
+ logGroupName=log_group['logGroupName'],
62
+ retentionInDays=7
63
+ )
64
+ ```
65
+
66
+ **Implementation:**
67
+ 1. Create Lambda function on schedule (e.g., every hour)
68
+ 2. Scan for edge log groups with function name pattern
69
+ 3. Apply retention policy if not already set
70
+
71
+ **Pros:**
72
+ - Simple to implement
73
+ - No CloudTrail dependency
74
+ - Can handle existing log groups
75
+
76
+ **Cons:**
77
+ - Not real-time (delayed retention)
78
+ - Runs periodically even when not needed
79
+
80
+ ### Solution 3: Post-Deployment Script
81
+ ```bash
82
+ #!/bin/bash
83
+ # Wait for edge log groups to appear
84
+ function_name="trav-talks-blue-green-edge-function"
85
+ edge_regions=("eu-central-1" "eu-west-1" "ap-southeast-1")
86
+
87
+ for region in "${edge_regions[@]}"; do
88
+ log_group="/aws/lambda/${region}.${function_name}"
89
+
90
+ # Wait for log group to exist
91
+ until aws logs describe-log-groups --log-group-name-prefix "$log_group" --region us-east-1; do
92
+ echo "Waiting for log group: $log_group"
93
+ sleep 30
94
+ done
95
+
96
+ # Set retention policy
97
+ aws logs put-retention-policy --log-group-name "$log_group" --retention-in-days 7 --region us-east-1
98
+ done
99
+ ```
100
+
101
+ **Implementation:**
102
+ 1. Script runs after Lambda@Edge deployment
103
+ 2. Waits for edge log groups to be created
104
+ 3. Sets retention policies when they appear
105
+
106
+ **Pros:**
107
+ - Direct control over timing
108
+ - No additional AWS resources needed
109
+
110
+ **Cons:**
111
+ - Manual process
112
+ - Hard to determine when log groups will appear
113
+ - Not automated
114
+
115
+ ### Solution 4: CloudWatch Logs Subscription
116
+ ```python
117
+ # Lambda triggered by log group creation via subscription filter
118
+ def lambda_handler(event, context):
119
+ for record in event['Records']:
120
+ log_group = record['logGroup']
121
+ if 'edge-function' in log_group:
122
+ # Set retention policy
123
+ logs.put_retention_policy(
124
+ logGroupName=log_group,
125
+ retentionInDays=7
126
+ )
127
+ ```
128
+
129
+ **Implementation:**
130
+ 1. Create subscription filter on log group pattern
131
+ 2. Lambda function triggered by log events
132
+ 3. Set retention policy on first log event
133
+
134
+ **Pros:**
135
+ - Event-driven
136
+ - No CloudTrail needed
137
+
138
+ **Cons:**
139
+ - Requires log group to exist first
140
+ - Complex subscription filter setup
141
+
142
+ ## 🎯 Recommended Implementation
143
+
144
+ ### Phase 1: Quick Win (Solution 2)
145
+ Implement periodic Lambda function as temporary solution:
146
+ - Easy to implement quickly
147
+ - Solves immediate problem
148
+ - Can be replaced later with better solution
149
+
150
+ ### Phase 2: Production Solution (Solution 1)
151
+ Implement EventBridge + Lambda for production:
152
+ - Real-time response
153
+ - Automatic handling
154
+ - Best long-term solution
155
+
156
+ ## 📋 Implementation Steps for Solution 1
157
+
158
+ ### 1. Create EventBridge Rule
159
+ ```python
160
+ event_rule = events.Rule(
161
+ self, "EdgeLogGroupRule",
162
+ event_pattern=events.EventPattern(
163
+ source=["aws.logs"],
164
+ detail_type=["AWS API Call via CloudTrail"],
165
+ detail={
166
+ "eventSource": ["logs.amazonaws.com"],
167
+ "eventName": ["CreateLogGroup"],
168
+ "requestParameters": {
169
+ "logGroupName": [{"prefix": "/aws/lambda/"}]
170
+ }
171
+ }
172
+ )
173
+ )
174
+ ```
175
+
176
+ ### 2. Create Lambda Function
177
+ ```python
178
+ retention_handler = _lambda.Function(
179
+ self, "EdgeLogRetentionHandler",
180
+ runtime=_lambda.Runtime.PYTHON_3_9,
181
+ handler="handler.lambda_handler",
182
+ code=_lambda.Code.from_asset("lambda/edge_log_retention"),
183
+ environment={
184
+ "RETENTION_DAYS": "7",
185
+ "FUNCTION_NAME_PATTERN": "*edge-function"
186
+ }
187
+ )
188
+ ```
189
+
190
+ ### 3. Add Permissions
191
+ ```python
192
+ retention_handler.add_to_role_policy(
193
+ iam.PolicyStatement(
194
+ actions=["logs:PutRetentionPolicy", "logs:DescribeLogGroups"],
195
+ resources=["*"]
196
+ )
197
+ )
198
+ ```
199
+
200
+ ### 4. Connect EventBridge to Lambda
201
+ ```python
202
+ event_rule.add_target(targets.LambdaFunction(retention_handler))
203
+ ```
204
+
205
+ ## 🔧 Current Configuration
206
+
207
+ The edge log retention configuration is currently **disabled** in the Lambda Edge stack:
208
+
209
+ ```python
210
+ def _configure_edge_log_retention(self, function_name: str) -> None:
211
+ # DISABLED: See implementation plan above
212
+ logger.warning("Edge log retention disabled - see TODO for implementation")
213
+ return
214
+ ```
215
+
216
+ ## 📊 Configuration Impact
217
+
218
+ | Setting | Current Behavior | Target Behavior |
219
+ |---------|------------------|-----------------|
220
+ | `edge_log_retention_days` | Warning logged, no action applied | Retention policy set on all edge log groups |
221
+ | Edge log groups | Created with default retention (never expire) | Created with specified retention (e.g., 7 days) |
222
+ | Cost impact | Potential high log storage costs | Controlled log storage costs |
223
+
224
+ ---
225
+
226
+ **Status**: Ready for implementation when edge log retention is required.
@@ -248,19 +248,12 @@ class LambdaEdgeStack(IStack, StandardizedSsmMixin):
248
248
  self.edge_config.runtime,
249
249
  _lambda.Runtime.PYTHON_3_11
250
250
  )
251
-
252
- # Lambda@Edge does NOT support environment variables
253
- # Configuration must be handled via:
254
- # 1. Hardcoded in the function code
255
- # 2. Fetched from SSM Parameter Store at runtime
256
- # 3. Other configuration mechanisms
257
-
251
+
258
252
  # Log warning if environment variables are configured
259
253
  if self.edge_config.environment:
260
254
  logger.warning(
261
255
  f"Lambda@Edge function '{function_name}' has environment variables configured, "
262
- "but Lambda@Edge does not support environment variables. "
263
- "The function must fetch these values from SSM Parameter Store at runtime."
256
+ "but Lambda@Edge does not support environment variables. The function must fetch these values from SSM Parameter Store at runtime."
264
257
  )
265
258
  for key, value in self.edge_config.environment.items():
266
259
  logger.warning(f" - {key}: {value}")
@@ -298,7 +291,20 @@ class LambdaEdgeStack(IStack, StandardizedSsmMixin):
298
291
  )
299
292
  )
300
293
 
301
- # Create the Lambda function WITHOUT environment variables
294
+ # Add Secrets Manager permissions for origin secret access
295
+ execution_role.add_to_policy(
296
+ iam.PolicyStatement(
297
+ effect=iam.Effect.ALLOW,
298
+ actions=[
299
+ "secretsmanager:GetSecretValue",
300
+ "secretsmanager:DescribeSecret"
301
+ ],
302
+ resources=[
303
+ f"arn:aws:secretsmanager:*:{cdk.Aws.ACCOUNT_ID}:secret:{self.deployment.environment}/{self.workload.name}/origin-secret*"
304
+ ]
305
+ )
306
+ )
307
+
302
308
  self.function = _lambda.Function(
303
309
  self,
304
310
  function_name,
@@ -311,6 +317,7 @@ class LambdaEdgeStack(IStack, StandardizedSsmMixin):
311
317
  description=self.edge_config.description,
312
318
  role=execution_role,
313
319
  # Lambda@Edge does NOT support environment variables
320
+ # Configuration must be fetched from SSM at runtime
314
321
  log_retention=logs.RetentionDays.ONE_WEEK,
315
322
  )
316
323
 
@@ -365,60 +372,45 @@ class LambdaEdgeStack(IStack, StandardizedSsmMixin):
365
372
 
366
373
  def _configure_edge_log_retention(self, function_name: str) -> None:
367
374
  """
368
- Configure log retention for Lambda@Edge regional logs.
375
+ Configure log retention for Lambda@Edge log groups in all edge regions
376
+
377
+ TODO: IMPLEMENT POST-DEPLOYMENT SOLUTION
378
+ --------------------------------------
379
+ Lambda@Edge log groups are created on-demand when the function is invoked
380
+ at edge locations, not during deployment. This means we cannot set retention
381
+ policies during CloudFormation deployment.
382
+
383
+ Possible solutions to implement:
384
+ 1. EventBridge rule that triggers on log group creation
385
+ 2. Custom Lambda function that runs periodically to set retention
386
+ 3. Post-deployment script that waits for log groups to appear
387
+ 4. CloudWatch Logs subscription filter that handles new log groups
369
388
 
370
- Lambda@Edge creates log groups in multiple regions that need
371
- separate retention configuration from the primary log group.
389
+ Current behavior: DISABLED to prevent deployment failures
372
390
  """
373
- from aws_cdk import custom_resources as cr
374
391
 
375
- # Get edge log retention from config (default to same as primary logs)
392
+ # DISABLED: Edge log groups don't exist during deployment
393
+ # Lambda@Edge creates log groups on-demand at edge locations
394
+ # Setting retention policies during deployment fails with "log group does not exist"
395
+
376
396
  edge_retention_days = self.edge_config.dictionary.get("edge_log_retention_days", 7)
397
+ logger.warning(
398
+ f"Edge log retention configuration disabled - log groups are created on-demand. "
399
+ f"Desired retention: {edge_retention_days} days. "
400
+ f"See TODO in _configure_edge_log_retention() for implementation approach."
401
+ )
377
402
 
378
- # List of common Lambda@Edge regions
379
- edge_regions = [
380
- 'us-east-1', 'us-east-2', 'us-west-1', 'us-west-2',
381
- 'eu-west-1', 'eu-west-2', 'eu-central-1',
382
- 'ap-southeast-1', 'ap-southeast-2', 'ap-northeast-1',
383
- 'ca-central-1', 'sa-east-1'
384
- ]
385
-
386
- # Create custom resource to set log retention for each region
387
- for region in edge_regions:
388
- log_group_name = f"/aws/lambda/{region}.{function_name}"
389
-
390
- # Use AwsCustomResource to set log retention
391
- cr.AwsCustomResource(
392
- self, f"EdgeLogRetention-{region}",
393
- on_update={
394
- "service": "Logs",
395
- "action": "putRetentionPolicy",
396
- "parameters": {
397
- "logGroupName": log_group_name,
398
- "retentionInDays": edge_retention_days
399
- },
400
- "physical_resource_id": cr.PhysicalResourceId.from_response("logGroupName")
401
- },
402
- on_delete={
403
- "service": "Logs",
404
- "action": "deleteRetentionPolicy",
405
- "parameters": {
406
- "logGroupName": log_group_name
407
- },
408
- "physical_resource_id": cr.PhysicalResourceId.from_response("logGroupName")
409
- },
410
- policy=cr.AwsCustomResourcePolicy.from_sdk_calls(
411
- resources=[f"arn:aws:logs:{region}:*:log-group:{log_group_name}*"]
412
- )
413
- )
403
+ # TODO: Implement one of these solutions:
404
+ # 1. EventBridge + Lambda: Trigger on log group creation and set retention
405
+ # 2. Periodic Lambda: Scan for edge log groups and apply retention policies
406
+ # 3. Post-deployment script: Wait for log groups to appear after edge replication
407
+ # 4. CloudWatch Logs subscription: Process new log group events
414
408
 
415
- logger.info(f"Configured edge log retention to {edge_retention_days} days for {len(edge_regions)} regions")
409
+ return
416
410
 
417
411
  def _add_outputs(self, function_name: str) -> None:
418
412
  """Add CloudFormation outputs and SSM exports"""
419
413
 
420
-
421
-
422
414
  # SSM Parameter Store exports (if configured)
423
415
  ssm_exports = self.edge_config.dictionary.get("ssm", {}).get("exports", {})
424
416
  if ssm_exports:
@@ -440,40 +432,31 @@ class LambdaEdgeStack(IStack, StandardizedSsmMixin):
440
432
  description=f"{key} for Lambda@Edge function {function_name}"
441
433
  )
442
434
 
443
- # Export environment variables as SSM parameters
444
- # Since Lambda@Edge doesn't support environment variables, we export them
445
- # to SSM so the Lambda function can fetch them at runtime
446
- if self.edge_config.environment:
447
- logger.info("Exporting Lambda@Edge environment variables as SSM parameters")
448
- env_ssm_exports = self.edge_config.dictionary.get("environment_ssm_exports", {})
449
-
450
- # If no explicit environment_ssm_exports, create default SSM paths
451
- if not env_ssm_exports:
452
- # Auto-generate SSM parameter names based on environment variable names
453
- for env_key in self.edge_config.environment.keys():
454
- # Use snake_case version of the key for SSM path
455
- ssm_key = env_key.lower().replace('_', '-')
456
- env_ssm_exports[env_key] = f"/{self.deployment.environment}/{function_name}/{ssm_key}"
457
-
458
- # Resolve and export environment variables to SSM
459
- resolved_env = self._resolve_environment_variables()
460
- for env_key, ssm_path in env_ssm_exports.items():
461
- if env_key in resolved_env:
462
- env_value = resolved_env[env_key]
463
-
464
- # Handle empty values - SSM doesn't allow empty strings
465
- # Use sentinel value "NONE" to indicate explicitly unset
466
- if not env_value or (isinstance(env_value, str) and env_value.strip() == ""):
467
- env_value = "NONE"
468
- logger.info(
469
- f"Environment variable {env_key} is empty - setting SSM parameter to 'NONE'. "
470
- f"Lambda function should treat 'NONE' as unset/disabled."
471
- )
472
-
473
- self.export_ssm_parameter(
474
- self,
475
- f"env-{env_key}-param",
476
- env_value,
477
- ssm_path,
478
- description=f"Configuration for Lambda@Edge: {env_key}"
479
- )
435
+ # Export the complete configuration as a single SSM parameter
436
+ config_ssm_path = f"/{self.deployment.environment}/{self.workload.name}/lambda-edge/config"
437
+ configuration = self.edge_config.dictionary.get("configuration", {})
438
+ environment_variables = configuration.get("environment_variables", {})
439
+
440
+ full_config = {
441
+ "environment_variables": environment_variables
442
+ }
443
+
444
+ self.export_ssm_parameter(
445
+ self,
446
+ "full-config-param",
447
+ json.dumps(full_config),
448
+ config_ssm_path,
449
+ description=f"Complete Lambda@Edge configuration for {function_name} - update this for dynamic changes"
450
+ )
451
+
452
+ # Export cache TTL parameter for dynamic cache control
453
+ cache_ttl_ssm_path = f"/{self.deployment.environment}/{self.workload.name}/lambda-edge/cache-ttl"
454
+ default_cache_ttl = self.edge_config.dictionary.get("cache_ttl_seconds", 300) # Default 5 minutes
455
+
456
+ self.export_ssm_parameter(
457
+ self,
458
+ "cache-ttl-param",
459
+ str(default_cache_ttl),
460
+ cache_ttl_ssm_path,
461
+ description=f"Lambda@Edge configuration cache TTL in seconds for {function_name} - adjust for maintenance windows (30-3600)"
462
+ )
cdk_factory/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.19.13"
1
+ __version__ = "0.19.19"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cdk_factory
3
- Version: 0.19.13
3
+ Version: 0.19.19
4
4
  Summary: CDK Factory. A QuickStarter and best practices setup for CDK projects
5
5
  Author-email: Eric Wilson <eric.wilson@geekcafe.com>
6
6
  License: MIT License
@@ -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=qfLAZUWz8ZIxPUsFWlqQCfCwTNmUwrL2jNL4w6ZRFi0,24
5
+ cdk_factory/version.py,sha256=7SkXrcCPPywUj5iTSUoEUN_6FonxdNwtTRVl_D3253s,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
@@ -100,8 +100,9 @@ cdk_factory/stack_library/ecr/ecr_stack.py,sha256=KLbd5WN5-ZiojsS5wJ4PX-tIL0cCyl
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
102
  cdk_factory/stack_library/ecs/ecs_service_stack.py,sha256=KB4YCIsMm5JIGM9Bm-bKcr3eX5xXFgnoA7jST_ekK44,28209
103
+ cdk_factory/stack_library/lambda_edge/EDGE_LOG_RETENTION_TODO.md,sha256=nD49nLm5OyrZUvcGNFBy9H1MfSUOuZ7sasHNI-IO0Zk,6635
103
104
  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=eHh_k4mbNp1prEnNvKqfK82lLNrMZS-7HAaUYzFEoOU,21040
105
+ cdk_factory/stack_library/lambda_edge/lambda_edge_stack.py,sha256=b8PE4v5zGsqu_mVOaPejb8JKkV6b83N-VS0AxSlLTtk,20119
105
106
  cdk_factory/stack_library/load_balancer/__init__.py,sha256=wZpKw2OecLJGdF5mPayCYAEhu2H3c2gJFFIxwXftGDU,52
106
107
  cdk_factory/stack_library/load_balancer/load_balancer_stack.py,sha256=ApW5q3SAvSJtiK0RInNljmubqXqKZU5QBAaUoeIW-pM,28287
107
108
  cdk_factory/stack_library/monitoring/__init__.py,sha256=k1G_KDx47Aw0UugaL99PN_TKlyLK4nkJVApCaAK7GJg,153
@@ -136,8 +137,8 @@ cdk_factory/utilities/os_execute.py,sha256=5Op0LY_8Y-pUm04y1k8MTpNrmQvcLmQHPQITE
136
137
  cdk_factory/utils/api_gateway_utilities.py,sha256=If7Xu5s_UxmuV-kL3JkXxPLBdSVUKoLtohm0IUFoiV8,4378
137
138
  cdk_factory/validation/config_validator.py,sha256=Pb0TkLiPFzUplBOgMorhRCVm08vEzZhRU5xXCDTa5CA,17602
138
139
  cdk_factory/workload/workload_factory.py,sha256=yDI3cRhVI5ELNDcJPLpk9UY54Uind1xQoV3spzT4z7E,6068
139
- cdk_factory-0.19.13.dist-info/METADATA,sha256=Rw6GGM7Hl5-md3ntKrGRfX2u1ppfZRfIextBHMW-FsE,2452
140
- cdk_factory-0.19.13.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
141
- cdk_factory-0.19.13.dist-info/entry_points.txt,sha256=S1DPe0ORcdiwEALMN_WIo3UQrW_g4YdQCLEsc_b0Swg,53
142
- cdk_factory-0.19.13.dist-info/licenses/LICENSE,sha256=NOtdOeLwg2il_XBJdXUPFPX8JlV4dqTdDGAd2-khxT8,1066
143
- cdk_factory-0.19.13.dist-info/RECORD,,
140
+ cdk_factory-0.19.19.dist-info/METADATA,sha256=syphI_KKN3_606LBfCKYQLU_3YX4ppprIwsKcAENNBk,2452
141
+ cdk_factory-0.19.19.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
142
+ cdk_factory-0.19.19.dist-info/entry_points.txt,sha256=S1DPe0ORcdiwEALMN_WIo3UQrW_g4YdQCLEsc_b0Swg,53
143
+ cdk_factory-0.19.19.dist-info/licenses/LICENSE,sha256=NOtdOeLwg2il_XBJdXUPFPX8JlV4dqTdDGAd2-khxT8,1066
144
+ cdk_factory-0.19.19.dist-info/RECORD,,