@cdklabs/cdk-appmod-catalog-blueprints 1.0.0
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.
- package/.jsii +8644 -0
- package/LICENSE +202 -0
- package/README.md +212 -0
- package/lib/document-processing/agentic-document-processing.d.ts +16 -0
- package/lib/document-processing/agentic-document-processing.js +90 -0
- package/lib/document-processing/base-document-processing.d.ts +189 -0
- package/lib/document-processing/base-document-processing.js +509 -0
- package/lib/document-processing/bedrock-document-processing.d.ts +167 -0
- package/lib/document-processing/bedrock-document-processing.js +297 -0
- package/lib/document-processing/index.d.ts +3 -0
- package/lib/document-processing/index.js +20 -0
- package/lib/document-processing/resources/default-bedrock-invoke/index.py +63 -0
- package/lib/document-processing/resources/default-bedrock-invoke/requirements.txt +4 -0
- package/lib/document-processing/resources/default-doc-retrieval-lambda/index.mjs +92 -0
- package/lib/document-processing/resources/default-doc-retrieval-lambda/package.json +10 -0
- package/lib/document-processing/resources/default-error-handler/index.js +46 -0
- package/lib/document-processing/resources/default-error-handler/package.json +4 -0
- package/lib/document-processing/resources/default-image-processor/classifier.mjs +665 -0
- package/lib/document-processing/resources/default-image-processor/extractors.mjs +465 -0
- package/lib/document-processing/resources/default-image-processor/index.mjs +143 -0
- package/lib/document-processing/resources/default-image-processor/package-lock.json +12 -0
- package/lib/document-processing/resources/default-image-processor/package.json +4 -0
- package/lib/document-processing/resources/default-image-validator/index.mjs +76 -0
- package/lib/document-processing/resources/default-image-validator/package-lock.json +154 -0
- package/lib/document-processing/resources/default-image-validator/package.json +7 -0
- package/lib/document-processing/resources/default-pdf-processor/index.js +46 -0
- package/lib/document-processing/resources/default-pdf-validator/index.js +36 -0
- package/lib/document-processing/resources/default-sqs-consumer/index.py +111 -0
- package/lib/document-processing/resources/default-sqs-consumer/requirements.txt +4 -0
- package/lib/document-processing/resources/default-sqs-consumer/sample_payload.json +20 -0
- package/lib/document-processing/resources/default-sqs-consumer/sample_payload_multi.json +24 -0
- package/lib/document-processing/resources/default-strands-agent/index.py +111 -0
- package/lib/document-processing/resources/default-strands-agent/requirements.txt +6 -0
- package/lib/document-processing/tests/agentic-document-processing-nag.test.d.ts +1 -0
- package/lib/document-processing/tests/agentic-document-processing-nag.test.js +107 -0
- package/lib/document-processing/tests/agentic-document-processing.test.d.ts +1 -0
- package/lib/document-processing/tests/agentic-document-processing.test.js +125 -0
- package/lib/document-processing/tests/bedrock-document-processing-nag.test.d.ts +1 -0
- package/lib/document-processing/tests/bedrock-document-processing-nag.test.js +101 -0
- package/lib/document-processing/tests/bedrock-document-processing.test.d.ts +1 -0
- package/lib/document-processing/tests/bedrock-document-processing.test.js +79 -0
- package/lib/framework/custom-resource/default-runtimes.d.ts +21 -0
- package/lib/framework/custom-resource/default-runtimes.js +34 -0
- package/lib/framework/custom-resource/index.d.ts +1 -0
- package/lib/framework/custom-resource/index.js +18 -0
- package/lib/framework/foundation/access-log.d.ts +69 -0
- package/lib/framework/foundation/access-log.js +121 -0
- package/lib/framework/foundation/eventbridge-broker.d.ts +18 -0
- package/lib/framework/foundation/eventbridge-broker.js +42 -0
- package/lib/framework/foundation/index.d.ts +3 -0
- package/lib/framework/foundation/index.js +20 -0
- package/lib/framework/foundation/network.d.ts +19 -0
- package/lib/framework/foundation/network.js +83 -0
- package/lib/framework/index.d.ts +2 -0
- package/lib/framework/index.js +19 -0
- package/lib/framework/quickstart/base-quickstart.d.ts +30 -0
- package/lib/framework/quickstart/base-quickstart.js +30 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.js +21 -0
- package/lib/tsconfig.tsbuildinfo +1 -0
- package/lib/utilities/cdk-nag-config.d.ts +42 -0
- package/lib/utilities/cdk-nag-config.js +194 -0
- package/lib/utilities/data-loader-lambda/index.py +282 -0
- package/lib/utilities/data-loader-lambda/requirements.txt +3 -0
- package/lib/utilities/data-loader.d.ts +173 -0
- package/lib/utilities/data-loader.js +447 -0
- package/lib/utilities/index.d.ts +3 -0
- package/lib/utilities/index.js +20 -0
- package/lib/utilities/lambda-iam-utils.d.ts +145 -0
- package/lib/utilities/lambda-iam-utils.js +235 -0
- package/lib/utilities/lambda_layers/data-masking/layer-construct.d.ts +42 -0
- package/lib/utilities/lambda_layers/data-masking/layer-construct.js +53 -0
- package/lib/utilities/lambda_layers/data-masking/layer-construct.ts +88 -0
- package/lib/utilities/observability/bedrock-observability.d.ts +18 -0
- package/lib/utilities/observability/bedrock-observability.js +131 -0
- package/lib/utilities/observability/cloudfront-distribution-observability-property-injector.d.ts +6 -0
- package/lib/utilities/observability/cloudfront-distribution-observability-property-injector.js +22 -0
- package/lib/utilities/observability/index.d.ts +6 -0
- package/lib/utilities/observability/index.js +25 -0
- package/lib/utilities/observability/lambda-observability-property-injector.d.ts +8 -0
- package/lib/utilities/observability/lambda-observability-property-injector.js +43 -0
- package/lib/utilities/observability/log-group-data-protection-props.d.ts +19 -0
- package/lib/utilities/observability/log-group-data-protection-props.js +5 -0
- package/lib/utilities/observability/observability.d.ts +83 -0
- package/lib/utilities/observability/observability.js +278 -0
- package/lib/utilities/observability/observable.d.ts +32 -0
- package/lib/utilities/observability/observable.js +3 -0
- package/lib/utilities/observability/powertools-config.d.ts +3 -0
- package/lib/utilities/observability/powertools-config.js +25 -0
- package/lib/utilities/observability/resources/bedrock-manage-logging-configuration/index.py +27 -0
- package/lib/utilities/observability/state-machine-observability-property-injector.d.ts +8 -0
- package/lib/utilities/observability/state-machine-observability-property-injector.js +49 -0
- package/lib/utilities/tests/data-loader-nag.test.d.ts +1 -0
- package/lib/utilities/tests/data-loader-nag.test.js +432 -0
- package/lib/utilities/tests/data-loader.test.d.ts +1 -0
- package/lib/utilities/tests/data-loader.test.js +284 -0
- package/lib/webapp/frontend-construct.d.ts +136 -0
- package/lib/webapp/frontend-construct.js +253 -0
- package/lib/webapp/index.d.ts +1 -0
- package/lib/webapp/index.js +18 -0
- package/lib/webapp/tests/frontend-construct-nag.test.d.ts +1 -0
- package/lib/webapp/tests/frontend-construct-nag.test.js +266 -0
- package/lib/webapp/tests/frontend-construct.test.d.ts +1 -0
- package/lib/webapp/tests/frontend-construct.test.js +385 -0
- package/package.json +183 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared CDK Nag configuration and suppressions for AppMod Use Case Blueprints
|
|
3
|
+
*
|
|
4
|
+
* This module provides common CDK Nag suppressions that can be reused across
|
|
5
|
+
* different use cases while maintaining security best practices.
|
|
6
|
+
*/
|
|
7
|
+
export interface NagSuppression {
|
|
8
|
+
id: string;
|
|
9
|
+
reason: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Common suppressions that apply across multiple use cases
|
|
13
|
+
*/
|
|
14
|
+
export declare const COMMON_NAG_SUPPRESSIONS: NagSuppression[];
|
|
15
|
+
/**
|
|
16
|
+
* Document processing specific suppressions
|
|
17
|
+
*/
|
|
18
|
+
export declare const DOCUMENT_PROCESSING_NAG_SUPPRESSIONS: NagSuppression[];
|
|
19
|
+
/**
|
|
20
|
+
* Web application specific suppressions
|
|
21
|
+
*/
|
|
22
|
+
export declare const WEBAPP_NAG_SUPPRESSIONS: NagSuppression[];
|
|
23
|
+
/**
|
|
24
|
+
* Frontend specific suppressions
|
|
25
|
+
*/
|
|
26
|
+
export declare const FRONTEND_NAG_SUPPRESSIONS: NagSuppression[];
|
|
27
|
+
/**
|
|
28
|
+
* Development/testing specific suppressions
|
|
29
|
+
*/
|
|
30
|
+
export declare const DEVELOPMENT_NAG_SUPPRESSIONS: NagSuppression[];
|
|
31
|
+
/**
|
|
32
|
+
* Production specific suppressions (stricter requirements)
|
|
33
|
+
*/
|
|
34
|
+
export declare const PRODUCTION_NAG_SUPPRESSIONS: NagSuppression[];
|
|
35
|
+
/**
|
|
36
|
+
* Utility function to get suppressions based on use case and environment
|
|
37
|
+
*/
|
|
38
|
+
export declare function getNagSuppressions(useCase: 'document-processing' | 'webapp' | 'frontend' | 'common', environment?: 'development' | 'staging' | 'production'): NagSuppression[];
|
|
39
|
+
/**
|
|
40
|
+
* Utility function to create custom suppressions for specific scenarios
|
|
41
|
+
*/
|
|
42
|
+
export declare function createCustomSuppression(id: string, reason: string): NagSuppression;
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shared CDK Nag configuration and suppressions for AppMod Use Case Blueprints
|
|
4
|
+
*
|
|
5
|
+
* This module provides common CDK Nag suppressions that can be reused across
|
|
6
|
+
* different use cases while maintaining security best practices.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.PRODUCTION_NAG_SUPPRESSIONS = exports.DEVELOPMENT_NAG_SUPPRESSIONS = exports.FRONTEND_NAG_SUPPRESSIONS = exports.WEBAPP_NAG_SUPPRESSIONS = exports.DOCUMENT_PROCESSING_NAG_SUPPRESSIONS = exports.COMMON_NAG_SUPPRESSIONS = void 0;
|
|
10
|
+
exports.getNagSuppressions = getNagSuppressions;
|
|
11
|
+
exports.createCustomSuppression = createCustomSuppression;
|
|
12
|
+
/**
|
|
13
|
+
* Common suppressions that apply across multiple use cases
|
|
14
|
+
*/
|
|
15
|
+
exports.COMMON_NAG_SUPPRESSIONS = [
|
|
16
|
+
{
|
|
17
|
+
id: 'AwsSolutions-IAM4',
|
|
18
|
+
reason: 'AWS managed policies provide appropriate permissions and are maintained by AWS',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: 'AwsSolutions-L1',
|
|
22
|
+
reason: 'Lambda runtime versions are managed at the application deployment level',
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
/**
|
|
26
|
+
* Document processing specific suppressions
|
|
27
|
+
*/
|
|
28
|
+
exports.DOCUMENT_PROCESSING_NAG_SUPPRESSIONS = [
|
|
29
|
+
...exports.COMMON_NAG_SUPPRESSIONS,
|
|
30
|
+
{
|
|
31
|
+
id: 'AwsSolutions-S3-1',
|
|
32
|
+
reason: 'Document processing bucket requires controlled public access for result delivery',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: 'AwsSolutions-S3-2',
|
|
36
|
+
reason: 'Public read access is managed by application-level security controls',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: 'AwsSolutions-IAM5',
|
|
40
|
+
reason: 'Document processing requires broad permissions across multiple AWS services (S3, Textract, SQS, Step Functions)',
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: 'AwsSolutions-SQS3',
|
|
44
|
+
reason: 'Dead letter queue configuration is handled by the base document processor',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: 'AwsSolutions-SF1',
|
|
48
|
+
reason: 'Step Functions logging configuration is environment-specific',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: 'AwsSolutions-SF2',
|
|
52
|
+
reason: 'X-Ray tracing is configured based on monitoring requirements',
|
|
53
|
+
},
|
|
54
|
+
];
|
|
55
|
+
/**
|
|
56
|
+
* Web application specific suppressions
|
|
57
|
+
*/
|
|
58
|
+
exports.WEBAPP_NAG_SUPPRESSIONS = [
|
|
59
|
+
...exports.COMMON_NAG_SUPPRESSIONS,
|
|
60
|
+
{
|
|
61
|
+
id: 'AwsSolutions-ECS2',
|
|
62
|
+
reason: 'Environment variables are managed securely through configuration management',
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: 'AwsSolutions-ECS4',
|
|
66
|
+
reason: 'Container insights are enabled based on monitoring requirements and cost considerations',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: 'AwsSolutions-IAM5',
|
|
70
|
+
reason: 'Web application requires broad permissions for ECS, RDS, and other AWS service integration',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: 'AwsSolutions-ELB2',
|
|
74
|
+
reason: 'Load balancer access logging is configured based on compliance requirements',
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: 'AwsSolutions-RDS2',
|
|
78
|
+
reason: 'Database encryption is configured based on data sensitivity classification',
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: 'AwsSolutions-RDS3',
|
|
82
|
+
reason: 'Multi-AZ configuration is based on availability SLA requirements',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: 'AwsSolutions-RDS6',
|
|
86
|
+
reason: 'Database authentication method is chosen based on application architecture',
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: 'AwsSolutions-RDS10',
|
|
90
|
+
reason: 'Database deletion protection is managed through deployment automation',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: 'AwsSolutions-RDS11',
|
|
94
|
+
reason: 'Standard database ports are acceptable within VPC security boundaries',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: 'AwsSolutions-SMG4',
|
|
98
|
+
reason: 'Secrets rotation policy is configured per organizational security standards',
|
|
99
|
+
},
|
|
100
|
+
];
|
|
101
|
+
/**
|
|
102
|
+
* Frontend specific suppressions
|
|
103
|
+
*/
|
|
104
|
+
exports.FRONTEND_NAG_SUPPRESSIONS = [
|
|
105
|
+
...exports.COMMON_NAG_SUPPRESSIONS,
|
|
106
|
+
{
|
|
107
|
+
id: 'AwsSolutions-CFR1',
|
|
108
|
+
reason: 'CloudFront geo restrictions are configured based on application requirements',
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
id: 'AwsSolutions-CFR2',
|
|
112
|
+
reason: 'CloudFront WAF integration is configured based on security requirements',
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
id: 'AwsSolutions-CFR3',
|
|
116
|
+
reason: 'CloudFront access logging is configured based on compliance requirements',
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
id: 'AwsSolutions-CFR4',
|
|
120
|
+
reason: 'CloudFront viewer protocol policy is set to redirect-to-https for security',
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
id: 'AwsSolutions-S1',
|
|
124
|
+
reason: 'S3 bucket access logging is configured based on compliance requirements',
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
id: 'AwsSolutions-S2',
|
|
128
|
+
reason: 'S3 bucket public access is blocked and access is controlled via CloudFront OAC',
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
id: 'AwsSolutions-S3',
|
|
132
|
+
reason: 'S3 bucket SSL requests only policy is enforced via CloudFront HTTPS redirect',
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
id: 'AwsSolutions-S10',
|
|
136
|
+
reason: 'S3 bucket MFA delete is managed through organizational security policies',
|
|
137
|
+
},
|
|
138
|
+
];
|
|
139
|
+
/**
|
|
140
|
+
* Development/testing specific suppressions
|
|
141
|
+
*/
|
|
142
|
+
exports.DEVELOPMENT_NAG_SUPPRESSIONS = [
|
|
143
|
+
{
|
|
144
|
+
id: 'AwsSolutions-RDS3',
|
|
145
|
+
reason: 'Multi-AZ not required for development environments',
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
id: 'AwsSolutions-ELB2',
|
|
149
|
+
reason: 'Access logging not required for development load balancers',
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
id: 'AwsSolutions-CFR3',
|
|
153
|
+
reason: 'CloudFront access logging not required for development environments',
|
|
154
|
+
},
|
|
155
|
+
];
|
|
156
|
+
/**
|
|
157
|
+
* Production specific suppressions (stricter requirements)
|
|
158
|
+
*/
|
|
159
|
+
exports.PRODUCTION_NAG_SUPPRESSIONS = [
|
|
160
|
+
// Production environments should have minimal suppressions
|
|
161
|
+
// Most security controls should be enabled
|
|
162
|
+
];
|
|
163
|
+
/**
|
|
164
|
+
* Utility function to get suppressions based on use case and environment
|
|
165
|
+
*/
|
|
166
|
+
function getNagSuppressions(useCase, environment = 'production') {
|
|
167
|
+
let suppressions = [];
|
|
168
|
+
switch (useCase) {
|
|
169
|
+
case 'document-processing':
|
|
170
|
+
suppressions = [...exports.DOCUMENT_PROCESSING_NAG_SUPPRESSIONS];
|
|
171
|
+
break;
|
|
172
|
+
case 'webapp':
|
|
173
|
+
suppressions = [...exports.WEBAPP_NAG_SUPPRESSIONS];
|
|
174
|
+
break;
|
|
175
|
+
case 'frontend':
|
|
176
|
+
suppressions = [...exports.FRONTEND_NAG_SUPPRESSIONS];
|
|
177
|
+
break;
|
|
178
|
+
case 'common':
|
|
179
|
+
suppressions = [...exports.COMMON_NAG_SUPPRESSIONS];
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
// Add environment-specific suppressions
|
|
183
|
+
if (environment === 'development') {
|
|
184
|
+
suppressions = [...suppressions, ...exports.DEVELOPMENT_NAG_SUPPRESSIONS];
|
|
185
|
+
}
|
|
186
|
+
return suppressions;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Utility function to create custom suppressions for specific scenarios
|
|
190
|
+
*/
|
|
191
|
+
function createCustomSuppression(id, reason) {
|
|
192
|
+
return { id, reason };
|
|
193
|
+
}
|
|
194
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2RrLW5hZy1jb25maWcuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi91c2UtY2FzZXMvdXRpbGl0aWVzL2Nkay1uYWctY29uZmlnLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7Ozs7R0FLRzs7O0FBdUtILGdEQTJCQztBQUtELDBEQUVDO0FBbE1EOztHQUVHO0FBQ1UsUUFBQSx1QkFBdUIsR0FBcUI7SUFDdkQ7UUFDRSxFQUFFLEVBQUUsbUJBQW1CO1FBQ3ZCLE1BQU0sRUFBRSxnRkFBZ0Y7S0FDekY7SUFDRDtRQUNFLEVBQUUsRUFBRSxpQkFBaUI7UUFDckIsTUFBTSxFQUFFLHlFQUF5RTtLQUNsRjtDQUNGLENBQUM7QUFFRjs7R0FFRztBQUNVLFFBQUEsb0NBQW9DLEdBQXFCO0lBQ3BFLEdBQUcsK0JBQXVCO0lBQzFCO1FBQ0UsRUFBRSxFQUFFLG1CQUFtQjtRQUN2QixNQUFNLEVBQUUsa0ZBQWtGO0tBQzNGO0lBQ0Q7UUFDRSxFQUFFLEVBQUUsbUJBQW1CO1FBQ3ZCLE1BQU0sRUFBRSxzRUFBc0U7S0FDL0U7SUFDRDtRQUNFLEVBQUUsRUFBRSxtQkFBbUI7UUFDdkIsTUFBTSxFQUFFLGlIQUFpSDtLQUMxSDtJQUNEO1FBQ0UsRUFBRSxFQUFFLG1CQUFtQjtRQUN2QixNQUFNLEVBQUUsMkVBQTJFO0tBQ3BGO0lBQ0Q7UUFDRSxFQUFFLEVBQUUsa0JBQWtCO1FBQ3RCLE1BQU0sRUFBRSw4REFBOEQ7S0FDdkU7SUFDRDtRQUNFLEVBQUUsRUFBRSxrQkFBa0I7UUFDdEIsTUFBTSxFQUFFLDhEQUE4RDtLQUN2RTtDQUNGLENBQUM7QUFFRjs7R0FFRztBQUNVLFFBQUEsdUJBQXVCLEdBQXFCO0lBQ3ZELEdBQUcsK0JBQXVCO0lBQzFCO1FBQ0UsRUFBRSxFQUFFLG1CQUFtQjtRQUN2QixNQUFNLEVBQUUsNkVBQTZFO0tBQ3RGO0lBQ0Q7UUFDRSxFQUFFLEVBQUUsbUJBQW1CO1FBQ3ZCLE1BQU0sRUFBRSx5RkFBeUY7S0FDbEc7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QUM7QUFFRDs7R0FFRztBQUNILFNBQWdCLHVCQUF1QixDQUFDLEVBQVUsRUFBRSxNQUFjO0lBQ2hFLE9BQU8sRUFBRSxFQUFFLEVBQUUsTUFBTSxFQUFFLENBQUM7QUFDeEIsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogU2hhcmVkIENESyBOYWcgY29uZmlndXJhdGlvbiBhbmQgc3VwcHJlc3Npb25zIGZvciBBcHBNb2QgVXNlIENhc2UgQmx1ZXByaW50c1xuICpcbiAqIFRoaXMgbW9kdWxlIHByb3ZpZGVzIGNvbW1vbiBDREsgTmFnIHN1cHByZXNzaW9ucyB0aGF0IGNhbiBiZSByZXVzZWQgYWNyb3NzXG4gKiBkaWZmZXJlbnQgdXNlIGNhc2VzIHdoaWxlIG1haW50YWluaW5nIHNlY3VyaXR5IGJlc3QgcHJhY3RpY2VzLlxuICovXG5cbmV4cG9ydCBpbnRlcmZhY2UgTmFnU3VwcHJlc3Npb24ge1xuICBpZDogc3RyaW5nO1xuICByZWFzb246IHN0cmluZztcbn1cblxuLyoqXG4gKiBDb21tb24gc3VwcHJlc3Npb25zIHRoYXQgYXBwbHkgYWNyb3NzIG11bHRpcGxlIHVzZSBjYXNlc1xuICovXG5leHBvcnQgY29uc3QgQ09NTU9OX05BR19TVVBQUkVTU0lPTlM6IE5hZ1N1cHByZXNzaW9uW10gPSBbXG4gIHtcbiAgICBpZDogJ0F3c1NvbHV0aW9ucy1JQU00JyxcbiAgICByZWFzb246ICdBV1MgbWFuYWdlZCBwb2xpY2llcyBwcm92aWRlIGFwcHJvcHJpYXRlIHBlcm1pc3Npb25zIGFuZCBhcmUgbWFpbnRhaW5lZCBieSBBV1MnLFxuICB9LFxuICB7XG4gICAgaWQ6ICdBd3NTb2x1dGlvbnMtTDEnLFxuICAgIHJlYXNvbjogJ0xhbWJkYSBydW50aW1lIHZlcnNpb25zIGFyZSBtYW5hZ2VkIGF0IHRoZSBhcHBsaWNhdGlvbiBkZXBsb3ltZW50IGxldmVsJyxcbiAgfSxcbl07XG5cbi8qKlxuICogRG9jdW1lbnQgcHJvY2Vzc2luZyBzcGVjaWZpYyBzdXBwcmVzc2lvbnNcbiAqL1xuZXhwb3J0IGNvbnN0IERPQ1VNRU5UX1BST0NFU1NJTkdfTkFHX1NVUFBSRVNTSU9OUzogTmFnU3VwcHJlc3Npb25bXSA9IFtcbiAgLi4uQ09NTU9OX05BR19TVVBQUkVTU0lPTlMsXG4gIHtcbiAgICBpZDogJ0F3c1NvbHV0aW9ucy1TMy0xJyxcbiAgICByZWFzb246ICdEb2N1bWVudCBwcm9jZXNzaW5nIGJ1Y2tldCByZXF1aXJlcyBjb250cm9sbGVkIHB1YmxpYyBhY2Nlc3MgZm9yIHJlc3VsdCBkZWxpdmVyeScsXG4gIH0sXG4gIHtcbiAgICBpZDogJ0F3c1NvbHV0aW9ucy1TMy0yJyxcbiAgICByZWFzb246ICdQdWJsaWMgcmVhZCBhY2Nlc3MgaXMgbWFuYWdlZCBieSBhcHBsaWNhdGlvbi1sZXZlbCBzZWN1cml0eSBjb250cm9scycsXG4gIH0sXG4gIHtcbiAgICBpZDogJ0F3c1NvbHV0aW9ucy1JQU01JyxcbiAgICByZWFzb246ICdEb2N1bWVudCBwcm9jZXNzaW5nIHJlcXVpcmVzIGJyb2FkIHBlcm1pc3Npb25zIGFjcm9zcyBtdWx0aXBsZSBBV1Mgc2VydmljZXMgKFMzLCBUZXh0cmFjdCwgU1FTLCBTdGVwIEZ1bmN0aW9ucyknLFxuICB9LFxuICB7XG4gICAgaWQ6ICdBd3NTb2x1dGlvbnMtU1FTMycsXG4gICAgcmVhc29uOiAnRGVhZCBsZXR0ZXIgcXVldWUgY29uZmlndXJhdGlvbiBpcyBoYW5kbGVkIGJ5IHRoZSBiYXNlIGRvY3VtZW50IHByb2Nlc3NvcicsXG4gIH0sXG4gIHtcbiAgICBpZDogJ0F3c1NvbHV0aW9ucy1TRjEnLFxuICAgIHJlYXNvbjogJ1N0ZXAgRnVuY3Rpb25zIGxvZ2dpbmcgY29uZmlndXJhdGlvbiBpcyBlbnZpcm9ubWVudC1zcGVjaWZpYycsXG4gIH0sXG4gIHtcbiAgICBpZDogJ0F3c1NvbHV0aW9ucy1TRjInLFxuICAgIHJlYXNvbjogJ1gtUmF5IHRyYWNpbmcgaXMgY29uZmlndXJlZCBiYXNlZCBvbiBtb25pdG9yaW5nIHJlcXVpcmVtZW50cycsXG4gIH0sXG5dO1xuXG4vKipcbiAqIFdlYiBhcHBsaWNhdGlvbiBzcGVjaWZpYyBzdXBwcmVzc2lvbnNcbiAqL1xuZXhwb3J0IGNvbnN0IFdFQkFQUF9OQUdfU1VQUFJFU1NJT05TOiBOYWdTdXBwcmVzc2lvbltdID0gW1xuICAuLi5DT01NT05fTkFHX1NVUFBSRVNTSU9OUyxcbiAge1xuICAgIGlkOiAnQXdzU29sdXRpb25zLUVDUzInLFxuICAgIHJlYXNvbjogJ0Vudmlyb25tZW50IHZhcmlhYmxlcyBhcmUgbWFuYWdlZCBzZWN1cmVseSB0aHJvdWdoIGNvbmZpZ3VyYXRpb24gbWFuYWdlbWVudCcsXG4gIH0sXG4gIHtcbiAgICBpZDogJ0F3c1NvbHV0aW9ucy1FQ1M0JyxcbiAgICByZWFzb246ICdDb250YWluZXIgaW5zaWdodHMgYXJlIGVuYWJsZWQgYmFzZWQgb24gbW9uaXRvcmluZyByZXF1aXJlbWVudHMgYW5kIGNvc3QgY29uc2lkZXJhdGlvbnMnLFxuICB9LFxuICB7XG4gICAgaWQ6ICdBd3NTb2x1dGlvbnMtSUFNNScsXG4gICAgcmVhc29uOiAnV2ViIGFwcGxpY2F0aW9uIHJlcXVpcmVzIGJyb2FkIHBlcm1pc3Npb25zIGZvciBFQ1MsIFJEUywgYW5kIG90aGVyIEFXUyBzZXJ2aWNlIGludGVncmF0aW9uJyxcbiAgfSxcbiAge1xuICAgIGlkOiAnQXdzU29sdXRpb25zLUVMQjInLFxuICAgIHJlYXNvbjogJ0xvYWQgYmFsYW5jZXIgYWNjZXNzIGxvZ2dpbmcgaXMgY29uZmlndXJlZCBiYXNlZCBvbiBjb21wbGlhbmNlIHJlcXVpcmVtZW50cycsXG4gIH0sXG4gIHtcbiAgICBpZDogJ0F3c1NvbHV0aW9ucy1SRFMyJyxcbiAgICByZWFzb246ICdEYXRhYmFzZSBlbmNyeXB0aW9uIGlzIGNvbmZpZ3VyZWQgYmFzZWQgb24gZGF0YSBzZW5zaXRpdml0eSBjbGFzc2lmaWNhdGlvbicsXG4gIH0sXG4gIHtcbiAgICBpZDogJ0F3c1NvbHV0aW9ucy1SRFMzJyxcbiAgICByZWFzb246ICdNdWx0aS1BWiBjb25maWd1cmF0aW9uIGlzIGJhc2VkIG9uIGF2YWlsYWJpbGl0eSBTTEEgcmVxdWlyZW1lbnRzJyxcbiAgfSxcbiAge1xuICAgIGlkOiAnQXdzU29sdXRpb25zLVJEUzYnLFxuICAgIHJlYXNvbjogJ0RhdGFiYXNlIGF1dGhlbnRpY2F0aW9uIG1ldGhvZCBpcyBjaG9zZW4gYmFzZWQgb24gYXBwbGljYXRpb24gYXJjaGl0ZWN0dXJlJyxcbiAgfSxcbiAge1xuICAgIGlkOiAnQXdzU29sdXRpb25zLVJEUzEwJyxcbiAgICByZWFzb246ICdEYXRhYmFzZSBkZWxldGlvbiBwcm90ZWN0aW9uIGlzIG1hbmFnZWQgdGhyb3VnaCBkZXBsb3ltZW50IGF1dG9tYXRpb24nLFxuICB9LFxuICB7XG4gICAgaWQ6ICdBd3NTb2x1dGlvbnMtUkRTMTEnLFxuICAgIHJlYXNvbjogJ1N0YW5kYXJkIGRhdGFiYXNlIHBvcnRzIGFyZSBhY2NlcHRhYmxlIHdpdGhpbiBWUEMgc2VjdXJpdHkgYm91bmRhcmllcycsXG4gIH0sXG4gIHtcbiAgICBpZDogJ0F3c1NvbHV0aW9ucy1TTUc0JyxcbiAgICByZWFzb246ICdTZWNyZXRzIHJvdGF0aW9uIHBvbGljeSBpcyBjb25maWd1cmVkIHBlciBvcmdhbml6YXRpb25hbCBzZWN1cml0eSBzdGFuZGFyZHMnLFxuICB9LFxuXTtcblxuLyoqXG4gKiBGcm9udGVuZCBzcGVjaWZpYyBzdXBwcmVzc2lvbnNcbiAqL1xuZXhwb3J0IGNvbnN0IEZST05URU5EX05BR19TVVBQUkVTU0lPTlM6IE5hZ1N1cHByZXNzaW9uW10gPSBbXG4gIC4uLkNPTU1PTl9OQUdfU1VQUFJFU1NJT05TLFxuICB7XG4gICAgaWQ6ICdBd3NTb2x1dGlvbnMtQ0ZSMScsXG4gICAgcmVhc29uOiAnQ2xvdWRGcm9udCBnZW8gcmVzdHJpY3Rpb25zIGFyZSBjb25maWd1cmVkIGJhc2VkIG9uIGFwcGxpY2F0aW9uIHJlcXVpcmVtZW50cycsXG4gIH0sXG4gIHtcbiAgICBpZDogJ0F3c1NvbHV0aW9ucy1DRlIyJyxcbiAgICByZWFzb246ICdDbG91ZEZyb250IFdBRiBpbnRlZ3JhdGlvbiBpcyBjb25maWd1cmVkIGJhc2VkIG9uIHNlY3VyaXR5IHJlcXVpcmVtZW50cycsXG4gIH0sXG4gIHtcbiAgICBpZDogJ0F3c1NvbHV0aW9ucy1DRlIzJyxcbiAgICByZWFzb246ICdDbG91ZEZyb250IGFjY2VzcyBsb2dnaW5nIGlzIGNvbmZpZ3VyZWQgYmFzZWQgb24gY29tcGxpYW5jZSByZXF1aXJlbWVudHMnLFxuICB9LFxuICB7XG4gICAgaWQ6ICdBd3NTb2x1dGlvbnMtQ0ZSNCcsXG4gICAgcmVhc29uOiAnQ2xvdWRGcm9udCB2aWV3ZXIgcHJvdG9jb2wgcG9saWN5IGlzIHNldCB0byByZWRpcmVjdC10by1odHRwcyBmb3Igc2VjdXJpdHknLFxuICB9LFxuICB7XG4gICAgaWQ6ICdBd3NTb2x1dGlvbnMtUzEnLFxuICAgIHJlYXNvbjogJ1MzIGJ1Y2tldCBhY2Nlc3MgbG9nZ2luZyBpcyBjb25maWd1cmVkIGJhc2VkIG9uIGNvbXBsaWFuY2UgcmVxdWlyZW1lbnRzJyxcbiAgfSxcbiAge1xuICAgIGlkOiAnQXdzU29sdXRpb25zLVMyJyxcbiAgICByZWFzb246ICdTMyBidWNrZXQgcHVibGljIGFjY2VzcyBpcyBibG9ja2VkIGFuZCBhY2Nlc3MgaXMgY29udHJvbGxlZCB2aWEgQ2xvdWRGcm9udCBPQUMnLFxuICB9LFxuICB7XG4gICAgaWQ6ICdBd3NTb2x1dGlvbnMtUzMnLFxuICAgIHJlYXNvbjogJ1MzIGJ1Y2tldCBTU0wgcmVxdWVzdHMgb25seSBwb2xpY3kgaXMgZW5mb3JjZWQgdmlhIENsb3VkRnJvbnQgSFRUUFMgcmVkaXJlY3QnLFxuICB9LFxuICB7XG4gICAgaWQ6ICdBd3NTb2x1dGlvbnMtUzEwJyxcbiAgICByZWFzb246ICdTMyBidWNrZXQgTUZBIGRlbGV0ZSBpcyBtYW5hZ2VkIHRocm91Z2ggb3JnYW5pemF0aW9uYWwgc2VjdXJpdHkgcG9saWNpZXMnLFxuICB9LFxuXTtcblxuLyoqXG4gKiBEZXZlbG9wbWVudC90ZXN0aW5nIHNwZWNpZmljIHN1cHByZXNzaW9uc1xuICovXG5leHBvcnQgY29uc3QgREVWRUxPUE1FTlRfTkFHX1NVUFBSRVNTSU9OUzogTmFnU3VwcHJlc3Npb25bXSA9IFtcbiAge1xuICAgIGlkOiAnQXdzU29sdXRpb25zLVJEUzMnLFxuICAgIHJlYXNvbjogJ011bHRpLUFaIG5vdCByZXF1aXJlZCBmb3IgZGV2ZWxvcG1lbnQgZW52aXJvbm1lbnRzJyxcbiAgfSxcbiAge1xuICAgIGlkOiAnQXdzU29sdXRpb25zLUVMQjInLFxuICAgIHJlYXNvbjogJ0FjY2VzcyBsb2dnaW5nIG5vdCByZXF1aXJlZCBmb3IgZGV2ZWxvcG1lbnQgbG9hZCBiYWxhbmNlcnMnLFxuICB9LFxuICB7XG4gICAgaWQ6ICdBd3NTb2x1dGlvbnMtQ0ZSMycsXG4gICAgcmVhc29uOiAnQ2xvdWRGcm9udCBhY2Nlc3MgbG9nZ2luZyBub3QgcmVxdWlyZWQgZm9yIGRldmVsb3BtZW50IGVudmlyb25tZW50cycsXG4gIH0sXG5dO1xuXG4vKipcbiAqIFByb2R1Y3Rpb24gc3BlY2lmaWMgc3VwcHJlc3Npb25zIChzdHJpY3RlciByZXF1aXJlbWVudHMpXG4gKi9cbmV4cG9ydCBjb25zdCBQUk9EVUNUSU9OX05BR19TVVBQUkVTU0lPTlM6IE5hZ1N1cHByZXNzaW9uW10gPSBbXG4gIC8vIFByb2R1Y3Rpb24gZW52aXJvbm1lbnRzIHNob3VsZCBoYXZlIG1pbmltYWwgc3VwcHJlc3Npb25zXG4gIC8vIE1vc3Qgc2VjdXJpdHkgY29udHJvbHMgc2hvdWxkIGJlIGVuYWJsZWRcbl07XG5cbi8qKlxuICogVXRpbGl0eSBmdW5jdGlvbiB0byBnZXQgc3VwcHJlc3Npb25zIGJhc2VkIG9uIHVzZSBjYXNlIGFuZCBlbnZpcm9ubWVudFxuICovXG5leHBvcnQgZnVuY3Rpb24gZ2V0TmFnU3VwcHJlc3Npb25zKFxuICB1c2VDYXNlOiAnZG9jdW1lbnQtcHJvY2Vzc2luZycgfCAnd2ViYXBwJyB8ICdmcm9udGVuZCcgfCAnY29tbW9uJyxcbiAgZW52aXJvbm1lbnQ6ICdkZXZlbG9wbWVudCcgfCAnc3RhZ2luZycgfCAncHJvZHVjdGlvbicgPSAncHJvZHVjdGlvbicsXG4pOiBOYWdTdXBwcmVzc2lvbltdIHtcbiAgbGV0IHN1cHByZXNzaW9uczogTmFnU3VwcHJlc3Npb25bXSA9IFtdO1xuXG4gIHN3aXRjaCAodXNlQ2FzZSkge1xuICAgIGNhc2UgJ2RvY3VtZW50LXByb2Nlc3NpbmcnOlxuICAgICAgc3VwcHJlc3Npb25zID0gWy4uLkRPQ1VNRU5UX1BST0NFU1NJTkdfTkFHX1NVUFBSRVNTSU9OU107XG4gICAgICBicmVhaztcbiAgICBjYXNlICd3ZWJhcHAnOlxuICAgICAgc3VwcHJlc3Npb25zID0gWy4uLldFQkFQUF9OQUdfU1VQUFJFU1NJT05TXTtcbiAgICAgIGJyZWFrO1xuICAgIGNhc2UgJ2Zyb250ZW5kJzpcbiAgICAgIHN1cHByZXNzaW9ucyA9IFsuLi5GUk9OVEVORF9OQUdfU1VQUFJFU1NJT05TXTtcbiAgICAgIGJyZWFrO1xuICAgIGNhc2UgJ2NvbW1vbic6XG4gICAgICBzdXBwcmVzc2lvbnMgPSBbLi4uQ09NTU9OX05BR19TVVBQUkVTU0lPTlNdO1xuICAgICAgYnJlYWs7XG4gIH1cblxuICAvLyBBZGQgZW52aXJvbm1lbnQtc3BlY2lmaWMgc3VwcHJlc3Npb25zXG4gIGlmIChlbnZpcm9ubWVudCA9PT0gJ2RldmVsb3BtZW50Jykge1xuICAgIHN1cHByZXNzaW9ucyA9IFsuLi5zdXBwcmVzc2lvbnMsIC4uLkRFVkVMT1BNRU5UX05BR19TVVBQUkVTU0lPTlNdO1xuICB9XG5cbiAgcmV0dXJuIHN1cHByZXNzaW9ucztcbn1cblxuLyoqXG4gKiBVdGlsaXR5IGZ1bmN0aW9uIHRvIGNyZWF0ZSBjdXN0b20gc3VwcHJlc3Npb25zIGZvciBzcGVjaWZpYyBzY2VuYXJpb3NcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGNyZWF0ZUN1c3RvbVN1cHByZXNzaW9uKGlkOiBzdHJpbmcsIHJlYXNvbjogc3RyaW5nKTogTmFnU3VwcHJlc3Npb24ge1xuICByZXR1cm4geyBpZCwgcmVhc29uIH07XG59XG4iXX0=
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
DataLoader Lambda Function
|
|
4
|
+
|
|
5
|
+
This Lambda function processes individual files from S3 and loads them into the database.
|
|
6
|
+
It's called by Step Functions for each file that needs to be processed.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import logging
|
|
12
|
+
import boto3
|
|
13
|
+
import pymysql
|
|
14
|
+
import psycopg2
|
|
15
|
+
from typing import Dict, Any, Optional
|
|
16
|
+
import tempfile
|
|
17
|
+
import re
|
|
18
|
+
|
|
19
|
+
# Configure logging
|
|
20
|
+
logger = logging.getLogger()
|
|
21
|
+
logger.setLevel(logging.INFO)
|
|
22
|
+
|
|
23
|
+
# Initialize AWS clients
|
|
24
|
+
s3_client = boto3.client('s3')
|
|
25
|
+
secrets_client = boto3.client('secretsmanager')
|
|
26
|
+
|
|
27
|
+
class DatabaseConnection:
|
|
28
|
+
"""Handles database connections for MySQL and PostgreSQL"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, engine: str, connection_params: Dict[str, Any]):
|
|
31
|
+
self.engine = engine.lower()
|
|
32
|
+
self.connection_params = connection_params
|
|
33
|
+
self.connection = None
|
|
34
|
+
|
|
35
|
+
def connect(self):
|
|
36
|
+
"""Establish database connection"""
|
|
37
|
+
try:
|
|
38
|
+
if self.engine == 'mysql':
|
|
39
|
+
self.connection = pymysql.connect(
|
|
40
|
+
host=self.connection_params['host'],
|
|
41
|
+
port=self.connection_params.get('port', 3306),
|
|
42
|
+
user=self.connection_params['username'],
|
|
43
|
+
password=self.connection_params['password'],
|
|
44
|
+
database=self.connection_params['database'],
|
|
45
|
+
charset='utf8mb4',
|
|
46
|
+
autocommit=False
|
|
47
|
+
)
|
|
48
|
+
elif self.engine == 'postgresql':
|
|
49
|
+
self.connection = psycopg2.connect(
|
|
50
|
+
host=self.connection_params['host'],
|
|
51
|
+
port=self.connection_params.get('port', 5432),
|
|
52
|
+
user=self.connection_params['username'],
|
|
53
|
+
password=self.connection_params['password'],
|
|
54
|
+
database=self.connection_params['database']
|
|
55
|
+
)
|
|
56
|
+
self.connection.autocommit = False
|
|
57
|
+
else:
|
|
58
|
+
raise ValueError(f"Unsupported database engine: {self.engine}")
|
|
59
|
+
|
|
60
|
+
logger.info(f"Successfully connected to {self.engine} database")
|
|
61
|
+
|
|
62
|
+
except Exception as e:
|
|
63
|
+
logger.error(f"Failed to connect to database: {str(e)}")
|
|
64
|
+
raise
|
|
65
|
+
|
|
66
|
+
def execute_sql_file(self, file_content: str) -> Dict[str, Any]:
|
|
67
|
+
"""Execute SQL content from file against the database as a single operation"""
|
|
68
|
+
if not self.connection:
|
|
69
|
+
raise RuntimeError("Database connection not established")
|
|
70
|
+
|
|
71
|
+
results = {
|
|
72
|
+
'success': True,
|
|
73
|
+
'file_executed': True,
|
|
74
|
+
'errors': []
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
if not file_content.strip():
|
|
79
|
+
logger.info("No SQL content to execute after cleaning")
|
|
80
|
+
return results
|
|
81
|
+
|
|
82
|
+
logger.info("Executing entire SQL file content as a single transaction")
|
|
83
|
+
|
|
84
|
+
# Execute the entire file content at once using the appropriate method for each database
|
|
85
|
+
if self.engine == 'postgresql':
|
|
86
|
+
self._execute_postgresql_file(file_content)
|
|
87
|
+
elif self.engine == 'mysql':
|
|
88
|
+
self._execute_mysql_file(file_content)
|
|
89
|
+
|
|
90
|
+
logger.info("Successfully executed SQL file content")
|
|
91
|
+
|
|
92
|
+
except Exception as e:
|
|
93
|
+
logger.error(f"Failed to execute SQL file: {str(e)}")
|
|
94
|
+
results['success'] = False
|
|
95
|
+
results['file_executed'] = False
|
|
96
|
+
results['errors'].append(str(e))
|
|
97
|
+
raise
|
|
98
|
+
|
|
99
|
+
return results
|
|
100
|
+
|
|
101
|
+
def _execute_postgresql_file(self, sql_content: str):
|
|
102
|
+
"""Execute PostgreSQL file content as a single operation"""
|
|
103
|
+
cursor = self.connection.cursor()
|
|
104
|
+
try:
|
|
105
|
+
# PostgreSQL can handle multiple statements in a single execute call
|
|
106
|
+
cursor.execute(sql_content)
|
|
107
|
+
self.connection.commit()
|
|
108
|
+
except Exception as e:
|
|
109
|
+
self.connection.rollback()
|
|
110
|
+
raise
|
|
111
|
+
finally:
|
|
112
|
+
cursor.close()
|
|
113
|
+
|
|
114
|
+
def _execute_mysql_file(self, sql_content: str):
|
|
115
|
+
"""Execute MySQL file content as a single transaction"""
|
|
116
|
+
cursor = self.connection.cursor()
|
|
117
|
+
try:
|
|
118
|
+
# For MySQL, we need to split statements but execute them all in one transaction
|
|
119
|
+
statements = self._split_sql_statements(sql_content)
|
|
120
|
+
|
|
121
|
+
# Execute all statements in a single transaction
|
|
122
|
+
for statement in statements:
|
|
123
|
+
statement = statement.strip()
|
|
124
|
+
if statement and not statement.startswith('--'):
|
|
125
|
+
cursor.execute(statement)
|
|
126
|
+
|
|
127
|
+
# Commit all changes as one transaction
|
|
128
|
+
self.connection.commit()
|
|
129
|
+
except Exception as e:
|
|
130
|
+
self.connection.rollback()
|
|
131
|
+
raise
|
|
132
|
+
finally:
|
|
133
|
+
cursor.close()
|
|
134
|
+
|
|
135
|
+
def _split_sql_statements(self, sql_content: str) -> list:
|
|
136
|
+
"""Split SQL content into individual statements"""
|
|
137
|
+
# Remove comments
|
|
138
|
+
sql_content = re.sub(r'--.*?\n', '\n', sql_content)
|
|
139
|
+
sql_content = re.sub(r'/\*.*?\*/', '', sql_content, flags=re.DOTALL)
|
|
140
|
+
|
|
141
|
+
# Split by semicolon, but be careful with strings and functions
|
|
142
|
+
statements = []
|
|
143
|
+
current_statement = ""
|
|
144
|
+
in_string = False
|
|
145
|
+
string_char = None
|
|
146
|
+
|
|
147
|
+
i = 0
|
|
148
|
+
while i < len(sql_content):
|
|
149
|
+
char = sql_content[i]
|
|
150
|
+
|
|
151
|
+
if not in_string and char in ["'", '"']:
|
|
152
|
+
in_string = True
|
|
153
|
+
string_char = char
|
|
154
|
+
elif in_string and char == string_char:
|
|
155
|
+
# Check if it's escaped
|
|
156
|
+
if i > 0 and sql_content[i-1] != '\\':
|
|
157
|
+
in_string = False
|
|
158
|
+
string_char = None
|
|
159
|
+
elif not in_string and char == ';':
|
|
160
|
+
current_statement += char
|
|
161
|
+
if current_statement.strip():
|
|
162
|
+
statements.append(current_statement.strip())
|
|
163
|
+
current_statement = ""
|
|
164
|
+
i += 1
|
|
165
|
+
continue
|
|
166
|
+
|
|
167
|
+
current_statement += char
|
|
168
|
+
i += 1
|
|
169
|
+
|
|
170
|
+
# Add the last statement if it doesn't end with semicolon
|
|
171
|
+
if current_statement.strip():
|
|
172
|
+
statements.append(current_statement.strip())
|
|
173
|
+
|
|
174
|
+
return statements
|
|
175
|
+
|
|
176
|
+
def close(self):
|
|
177
|
+
"""Close database connection"""
|
|
178
|
+
if self.connection:
|
|
179
|
+
self.connection.close()
|
|
180
|
+
logger.info("Database connection closed")
|
|
181
|
+
|
|
182
|
+
def get_database_credentials(secret_arn: str) -> Dict[str, Any]:
|
|
183
|
+
"""Retrieve database credentials from AWS Secrets Manager"""
|
|
184
|
+
try:
|
|
185
|
+
response = secrets_client.get_secret_value(SecretId=secret_arn)
|
|
186
|
+
secret_data = json.loads(response['SecretString'])
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
'host': secret_data.get('host'),
|
|
190
|
+
'port': secret_data.get('port'),
|
|
191
|
+
'username': secret_data.get('username'),
|
|
192
|
+
'password': secret_data.get('password'),
|
|
193
|
+
'database': os.environ.get('DATABASE_NAME')
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
except Exception as e:
|
|
197
|
+
logger.error(f"Failed to retrieve database credentials: {str(e)}")
|
|
198
|
+
raise
|
|
199
|
+
|
|
200
|
+
def download_file_from_s3(bucket_name: str, s3_key: str) -> str:
|
|
201
|
+
"""Download file from S3 and return its content"""
|
|
202
|
+
try:
|
|
203
|
+
logger.info(f"Downloading file s3://{bucket_name}/{s3_key}")
|
|
204
|
+
|
|
205
|
+
# Create temporary file
|
|
206
|
+
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.sql')
|
|
207
|
+
temp_file_path = temp_file.name
|
|
208
|
+
temp_file.close()
|
|
209
|
+
|
|
210
|
+
# Download file
|
|
211
|
+
s3_client.download_file(bucket_name, s3_key, temp_file_path)
|
|
212
|
+
|
|
213
|
+
# Read file content
|
|
214
|
+
with open(temp_file_path, 'r', encoding='utf-8') as f:
|
|
215
|
+
content = f.read()
|
|
216
|
+
|
|
217
|
+
# Clean up temporary file
|
|
218
|
+
os.unlink(temp_file_path)
|
|
219
|
+
|
|
220
|
+
logger.info(f"Successfully downloaded and read file {s3_key}")
|
|
221
|
+
return content
|
|
222
|
+
|
|
223
|
+
except Exception as e:
|
|
224
|
+
logger.error(f"Failed to download file {s3_key}: {str(e)}")
|
|
225
|
+
raise
|
|
226
|
+
|
|
227
|
+
def handler(event, context):
|
|
228
|
+
"""Lambda function handler"""
|
|
229
|
+
logger.info(f"Processing event: {json.dumps(event)}")
|
|
230
|
+
|
|
231
|
+
try:
|
|
232
|
+
# Extract parameters from event
|
|
233
|
+
s3_key = event.get('s3Key')
|
|
234
|
+
bucket_name = event.get('bucketName') or os.environ.get('S3_BUCKET')
|
|
235
|
+
|
|
236
|
+
if not s3_key:
|
|
237
|
+
raise ValueError("s3Key is required in the event payload")
|
|
238
|
+
|
|
239
|
+
if not bucket_name:
|
|
240
|
+
raise ValueError("bucketName is required in the event payload or S3_BUCKET environment variable")
|
|
241
|
+
|
|
242
|
+
logger.info(f"Processing file: s3://{bucket_name}/{s3_key}")
|
|
243
|
+
|
|
244
|
+
# Download file content from S3
|
|
245
|
+
file_content = download_file_from_s3(bucket_name, s3_key)
|
|
246
|
+
|
|
247
|
+
# Get database credentials
|
|
248
|
+
secret_arn = os.environ['DATABASE_SECRET_ARN']
|
|
249
|
+
db_credentials = get_database_credentials(secret_arn)
|
|
250
|
+
|
|
251
|
+
# Create database connection
|
|
252
|
+
engine = os.environ['DATABASE_ENGINE']
|
|
253
|
+
db_connection = DatabaseConnection(engine, db_credentials)
|
|
254
|
+
db_connection.connect()
|
|
255
|
+
|
|
256
|
+
try:
|
|
257
|
+
# Execute the SQL file
|
|
258
|
+
result = db_connection.execute_sql_file(file_content)
|
|
259
|
+
|
|
260
|
+
logger.info(f"File processing completed successfully: {result}")
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
'statusCode': 200,
|
|
264
|
+
'body': {
|
|
265
|
+
'message': 'SQL file executed successfully',
|
|
266
|
+
's3Key': s3_key,
|
|
267
|
+
'result': result
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
finally:
|
|
272
|
+
db_connection.close()
|
|
273
|
+
|
|
274
|
+
except Exception as e:
|
|
275
|
+
logger.error(f"Error processing file: {str(e)}")
|
|
276
|
+
return {
|
|
277
|
+
'statusCode': 500,
|
|
278
|
+
'body': {
|
|
279
|
+
'error': str(e),
|
|
280
|
+
's3Key': event.get('s3Key', 'unknown')
|
|
281
|
+
}
|
|
282
|
+
}
|