@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.
Files changed (105) hide show
  1. package/.jsii +8644 -0
  2. package/LICENSE +202 -0
  3. package/README.md +212 -0
  4. package/lib/document-processing/agentic-document-processing.d.ts +16 -0
  5. package/lib/document-processing/agentic-document-processing.js +90 -0
  6. package/lib/document-processing/base-document-processing.d.ts +189 -0
  7. package/lib/document-processing/base-document-processing.js +509 -0
  8. package/lib/document-processing/bedrock-document-processing.d.ts +167 -0
  9. package/lib/document-processing/bedrock-document-processing.js +297 -0
  10. package/lib/document-processing/index.d.ts +3 -0
  11. package/lib/document-processing/index.js +20 -0
  12. package/lib/document-processing/resources/default-bedrock-invoke/index.py +63 -0
  13. package/lib/document-processing/resources/default-bedrock-invoke/requirements.txt +4 -0
  14. package/lib/document-processing/resources/default-doc-retrieval-lambda/index.mjs +92 -0
  15. package/lib/document-processing/resources/default-doc-retrieval-lambda/package.json +10 -0
  16. package/lib/document-processing/resources/default-error-handler/index.js +46 -0
  17. package/lib/document-processing/resources/default-error-handler/package.json +4 -0
  18. package/lib/document-processing/resources/default-image-processor/classifier.mjs +665 -0
  19. package/lib/document-processing/resources/default-image-processor/extractors.mjs +465 -0
  20. package/lib/document-processing/resources/default-image-processor/index.mjs +143 -0
  21. package/lib/document-processing/resources/default-image-processor/package-lock.json +12 -0
  22. package/lib/document-processing/resources/default-image-processor/package.json +4 -0
  23. package/lib/document-processing/resources/default-image-validator/index.mjs +76 -0
  24. package/lib/document-processing/resources/default-image-validator/package-lock.json +154 -0
  25. package/lib/document-processing/resources/default-image-validator/package.json +7 -0
  26. package/lib/document-processing/resources/default-pdf-processor/index.js +46 -0
  27. package/lib/document-processing/resources/default-pdf-validator/index.js +36 -0
  28. package/lib/document-processing/resources/default-sqs-consumer/index.py +111 -0
  29. package/lib/document-processing/resources/default-sqs-consumer/requirements.txt +4 -0
  30. package/lib/document-processing/resources/default-sqs-consumer/sample_payload.json +20 -0
  31. package/lib/document-processing/resources/default-sqs-consumer/sample_payload_multi.json +24 -0
  32. package/lib/document-processing/resources/default-strands-agent/index.py +111 -0
  33. package/lib/document-processing/resources/default-strands-agent/requirements.txt +6 -0
  34. package/lib/document-processing/tests/agentic-document-processing-nag.test.d.ts +1 -0
  35. package/lib/document-processing/tests/agentic-document-processing-nag.test.js +107 -0
  36. package/lib/document-processing/tests/agentic-document-processing.test.d.ts +1 -0
  37. package/lib/document-processing/tests/agentic-document-processing.test.js +125 -0
  38. package/lib/document-processing/tests/bedrock-document-processing-nag.test.d.ts +1 -0
  39. package/lib/document-processing/tests/bedrock-document-processing-nag.test.js +101 -0
  40. package/lib/document-processing/tests/bedrock-document-processing.test.d.ts +1 -0
  41. package/lib/document-processing/tests/bedrock-document-processing.test.js +79 -0
  42. package/lib/framework/custom-resource/default-runtimes.d.ts +21 -0
  43. package/lib/framework/custom-resource/default-runtimes.js +34 -0
  44. package/lib/framework/custom-resource/index.d.ts +1 -0
  45. package/lib/framework/custom-resource/index.js +18 -0
  46. package/lib/framework/foundation/access-log.d.ts +69 -0
  47. package/lib/framework/foundation/access-log.js +121 -0
  48. package/lib/framework/foundation/eventbridge-broker.d.ts +18 -0
  49. package/lib/framework/foundation/eventbridge-broker.js +42 -0
  50. package/lib/framework/foundation/index.d.ts +3 -0
  51. package/lib/framework/foundation/index.js +20 -0
  52. package/lib/framework/foundation/network.d.ts +19 -0
  53. package/lib/framework/foundation/network.js +83 -0
  54. package/lib/framework/index.d.ts +2 -0
  55. package/lib/framework/index.js +19 -0
  56. package/lib/framework/quickstart/base-quickstart.d.ts +30 -0
  57. package/lib/framework/quickstart/base-quickstart.js +30 -0
  58. package/lib/index.d.ts +4 -0
  59. package/lib/index.js +21 -0
  60. package/lib/tsconfig.tsbuildinfo +1 -0
  61. package/lib/utilities/cdk-nag-config.d.ts +42 -0
  62. package/lib/utilities/cdk-nag-config.js +194 -0
  63. package/lib/utilities/data-loader-lambda/index.py +282 -0
  64. package/lib/utilities/data-loader-lambda/requirements.txt +3 -0
  65. package/lib/utilities/data-loader.d.ts +173 -0
  66. package/lib/utilities/data-loader.js +447 -0
  67. package/lib/utilities/index.d.ts +3 -0
  68. package/lib/utilities/index.js +20 -0
  69. package/lib/utilities/lambda-iam-utils.d.ts +145 -0
  70. package/lib/utilities/lambda-iam-utils.js +235 -0
  71. package/lib/utilities/lambda_layers/data-masking/layer-construct.d.ts +42 -0
  72. package/lib/utilities/lambda_layers/data-masking/layer-construct.js +53 -0
  73. package/lib/utilities/lambda_layers/data-masking/layer-construct.ts +88 -0
  74. package/lib/utilities/observability/bedrock-observability.d.ts +18 -0
  75. package/lib/utilities/observability/bedrock-observability.js +131 -0
  76. package/lib/utilities/observability/cloudfront-distribution-observability-property-injector.d.ts +6 -0
  77. package/lib/utilities/observability/cloudfront-distribution-observability-property-injector.js +22 -0
  78. package/lib/utilities/observability/index.d.ts +6 -0
  79. package/lib/utilities/observability/index.js +25 -0
  80. package/lib/utilities/observability/lambda-observability-property-injector.d.ts +8 -0
  81. package/lib/utilities/observability/lambda-observability-property-injector.js +43 -0
  82. package/lib/utilities/observability/log-group-data-protection-props.d.ts +19 -0
  83. package/lib/utilities/observability/log-group-data-protection-props.js +5 -0
  84. package/lib/utilities/observability/observability.d.ts +83 -0
  85. package/lib/utilities/observability/observability.js +278 -0
  86. package/lib/utilities/observability/observable.d.ts +32 -0
  87. package/lib/utilities/observability/observable.js +3 -0
  88. package/lib/utilities/observability/powertools-config.d.ts +3 -0
  89. package/lib/utilities/observability/powertools-config.js +25 -0
  90. package/lib/utilities/observability/resources/bedrock-manage-logging-configuration/index.py +27 -0
  91. package/lib/utilities/observability/state-machine-observability-property-injector.d.ts +8 -0
  92. package/lib/utilities/observability/state-machine-observability-property-injector.js +49 -0
  93. package/lib/utilities/tests/data-loader-nag.test.d.ts +1 -0
  94. package/lib/utilities/tests/data-loader-nag.test.js +432 -0
  95. package/lib/utilities/tests/data-loader.test.d.ts +1 -0
  96. package/lib/utilities/tests/data-loader.test.js +284 -0
  97. package/lib/webapp/frontend-construct.d.ts +136 -0
  98. package/lib/webapp/frontend-construct.js +253 -0
  99. package/lib/webapp/index.d.ts +1 -0
  100. package/lib/webapp/index.js +18 -0
  101. package/lib/webapp/tests/frontend-construct-nag.test.d.ts +1 -0
  102. package/lib/webapp/tests/frontend-construct-nag.test.js +266 -0
  103. package/lib/webapp/tests/frontend-construct.test.d.ts +1 -0
  104. package/lib/webapp/tests/frontend-construct.test.js +385 -0
  105. 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
+ }
@@ -0,0 +1,3 @@
1
+ boto3>=1.26.0
2
+ pymysql>=1.0.2
3
+ psycopg2-binary>=2.9.5