eoapi-cdk 8.2.3 → 8.3.1

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,284 @@
1
+ "use strict";
2
+ var _a, _b;
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.StacItemLoader = exports.StacLoader = void 0;
5
+ const JSII_RTTI_SYMBOL_1 = Symbol.for("jsii.rtti");
6
+ const aws_cdk_lib_1 = require("aws-cdk-lib");
7
+ const constructs_1 = require("constructs");
8
+ const path = require("path");
9
+ /**
10
+ * AWS CDK Construct for STAC Object Loading Infrastructure
11
+ *
12
+ * The StacLoader creates a serverless, event-driven system for loading
13
+ * STAC (SpatioTemporal Asset Catalog) objects into a PostgreSQL database with
14
+ * the pgstac extension. This construct supports multiple ingestion pathways
15
+ * for flexible STAC object loading.
16
+ *
17
+ * ## Architecture Overview
18
+ *
19
+ * This construct creates the following AWS resources:
20
+ * - **SNS Topic**: Entry point for STAC objects and S3 event notifications
21
+ * - **SQS Queue**: Buffers and batches messages before processing (60-second visibility timeout)
22
+ * - **Dead Letter Queue**: Captures failed loading attempts after 5 retries
23
+ * - **Lambda Function**: Python function that processes batches and inserts objects into pgstac
24
+ *
25
+ * ## Data Flow
26
+ *
27
+ * The loader supports two primary data ingestion patterns:
28
+ *
29
+ * ### Direct STAC Object Publishing
30
+ * 1. STAC objects (JSON) are published directly to the SNS topic in message bodies
31
+ * 2. The SQS queue collects messages and batches them (up to {batchSize} objects or 1 minute window)
32
+ * 3. The Lambda function receives batches, validates objects, and inserts into pgstac
33
+ *
34
+ * ### S3 Event-Driven Loading
35
+ * 1. An S3 bucket is configured to send notifications to the SNS topic when json files are created
36
+ * 2. STAC objects are uploaded to S3 buckets as JSON/GeoJSON files
37
+ * 3. S3 event notifications are sent to the SNS topic when objects are uploaded
38
+ * 4. The Lambda function receives S3 events in the SQS message batch, fetches objects from S3, and loads into pgstac
39
+ *
40
+ * ## Batching Behavior
41
+ *
42
+ * The SQS-to-Lambda integration uses intelligent batching to optimize performance:
43
+ *
44
+ * - **Batch Size**: Lambda waits to receive up to `batchSize` messages (default: 500)
45
+ * - **Batching Window**: If fewer than `batchSize` messages are available, Lambda
46
+ * triggers after `maxBatchingWindow` minutes (default: 1 minute)
47
+ * - **Trigger Condition**: Lambda executes when EITHER condition is met first
48
+ * - **Concurrency**: Limited to `maxConcurrency` concurrent executions to prevent database overload
49
+ * - **Partial Failures**: Uses `reportBatchItemFailures` to retry only failed objects
50
+ *
51
+ * This approach balances throughput (larger batches = fewer database connections)
52
+ * with latency (time-based triggers prevent indefinite waiting).
53
+ *
54
+ * ## Error Handling and Dead Letter Queue
55
+ *
56
+ * Failed messages are sent to the dead letter queue after 5 processing attempts.
57
+ * **Important**: This construct provides NO automated handling of dead letter queue
58
+ * messages - monitoring, inspection, and reprocessing of failed objects is the
59
+ * responsibility of the implementing application.
60
+ *
61
+ * Consider implementing:
62
+ * - CloudWatch alarms on dead letter queue depth
63
+ * - Manual or automated reprocessing workflows
64
+ * - Logging and alerting for failed objects
65
+ * - Regular cleanup of old dead letter messages (14-day retention)
66
+ *
67
+ * ## Operational Characteristics
68
+ *
69
+ * - **Scalability**: Lambda scales automatically based on queue depth
70
+ * - **Reliability**: Dead letter queue captures failures for debugging
71
+ * - **Efficiency**: Batching optimizes database operations for high throughput
72
+ * - **Security**: Database credentials accessed via AWS Secrets Manager
73
+ * - **Observability**: CloudWatch logs retained for one week
74
+ *
75
+ * ## Prerequisites
76
+ *
77
+ * Before using this construct, ensure:
78
+ * - The pgstac database has collections loaded (objects require existing collection IDs)
79
+ * - Database credentials are stored in AWS Secrets Manager
80
+ * - The pgstac extension is properly installed and configured
81
+ *
82
+ * ## Usage Example
83
+ *
84
+ * ```typescript
85
+ * // Create database first
86
+ * const database = new PgStacDatabase(this, 'Database', {
87
+ * pgstacVersion: '0.9.5'
88
+ * });
89
+ *
90
+ * // Create Object loader
91
+ * const loader = new StacLoader(this, 'StacLoader', {
92
+ * pgstacDb: database,
93
+ * batchSize: 1000, // Process up to 1000 objects per batch
94
+ * maxBatchingWindowMinutes: 1, // Wait max 1 minute to fill batch
95
+ * lambdaTimeoutSeconds: 300 // Allow up to 300 seconds for database operations
96
+ * });
97
+ *
98
+ * // The topic ARN can be used by other services to publish objects
99
+ * new CfnOutput(this, 'LoaderTopicArn', {
100
+ * value: loader.topic.topicArn
101
+ * });
102
+ * ```
103
+ *
104
+ * ## Direct Object Publishing
105
+ *
106
+ * External services can publish STAC objects directly to the topic:
107
+ *
108
+ * ```bash
109
+ * aws sns publish --topic-arn $STAC_LOAD_TOPIC --message '{
110
+ * "id": "example-collection",
111
+ * "type": "Collection",
112
+ * "title": "Example Collection",
113
+ * "description": "An example collection",
114
+ * "license": "proprietary",
115
+ * "extent": {
116
+ * "spatial": {"bbox": [[-180, -90, 180, 90]]},
117
+ * "temporal": {"interval": [[null, null]]},
118
+ * },
119
+ * "stac_version": "1.1.0",
120
+ * }'
121
+ *
122
+ * aws sns publish --topic-arn $STAC_LOAD_TOPIC --message '{
123
+ * "type": "Feature",
124
+ * "stac_version": "1.0.0",
125
+ * "id": "example-item",
126
+ * "properties": {"datetime": "2021-01-01T00:00:00Z"},
127
+ * "geometry": {"type": "Polygon", "coordinates": [...]},
128
+ * "collection": "example-collection"
129
+ * }'
130
+ *
131
+ *
132
+ * ```
133
+ *
134
+ * ## S3 Event Configuration
135
+ *
136
+ * To enable S3 event-driven loading, configure S3 bucket notifications to send
137
+ * events to the SNS topic when STAC objects (.json or .geojson files) are uploaded:
138
+ *
139
+ * ```typescript
140
+ * // Configure S3 bucket to send notifications to the loader topic
141
+ * bucket.addEventNotification(
142
+ * s3.EventType.OBJECT_CREATED,
143
+ * new s3n.SnsDestination(loader.topic),
144
+ * { suffix: '.json' }
145
+ * );
146
+ *
147
+ * bucket.addEventNotification(
148
+ * s3.EventType.OBJECT_CREATED,
149
+ * new s3n.SnsDestination(loader.topic),
150
+ * { suffix: '.geojson' }
151
+ * );
152
+ * ```
153
+ *
154
+ * When STAC objects are uploaded to the configured S3 bucket, the loader will:
155
+ * 1. Receive S3 event notifications via SNS
156
+ * 2. Fetch the STAC JSON from S3
157
+ * 3. Validate and load the objects into the pgstac database
158
+ *
159
+ * ## Monitoring and Troubleshooting
160
+ *
161
+ * - Monitor Lambda logs: `/aws/lambda/{FunctionName}`
162
+ * - **Dead Letter Queue**: Check for failed objects - **no automated handling provided**
163
+ * - Use batch objects failure reporting for partial batch processing
164
+ * - CloudWatch metrics available for queue depth and Lambda performance
165
+ *
166
+ * ### Dead Letter Queue Management
167
+ *
168
+ * Applications must implement their own dead letter queue monitoring:
169
+ *
170
+ * ```typescript
171
+ * // Example: CloudWatch alarm for dead letter queue depth
172
+ * new cloudwatch.Alarm(this, 'DeadLetterAlarm', {
173
+ * metric: loader.deadLetterQueue.metricApproximateNumberOfVisibleMessages(),
174
+ * threshold: 1,
175
+ * evaluationPeriods: 1
176
+ * });
177
+ *
178
+ * // Example: Lambda to reprocess dead letter messages
179
+ * const reprocessFunction = new lambda.Function(this, 'Reprocess', {
180
+ * // Implementation to fetch and republish failed messages
181
+ * });
182
+ * ```
183
+ *
184
+ */
185
+ class StacLoader extends constructs_1.Construct {
186
+ constructor(scope, id, props) {
187
+ super(scope, id);
188
+ const timeoutSeconds = props.lambdaTimeoutSeconds ?? 300;
189
+ const lambdaRuntime = props.lambdaRuntime ?? aws_cdk_lib_1.aws_lambda.Runtime.PYTHON_3_11;
190
+ const maxConcurrency = props.maxConcurrency ?? 2;
191
+ // Create dead letter queue
192
+ this.deadLetterQueue = new aws_cdk_lib_1.aws_sqs.Queue(this, "DeadLetterQueue", {
193
+ retentionPeriod: aws_cdk_lib_1.Duration.days(14),
194
+ });
195
+ // Create main queue
196
+ this.queue = new aws_cdk_lib_1.aws_sqs.Queue(this, "Queue", {
197
+ visibilityTimeout: aws_cdk_lib_1.Duration.seconds(timeoutSeconds + 10),
198
+ encryption: aws_cdk_lib_1.aws_sqs.QueueEncryption.SQS_MANAGED,
199
+ deadLetterQueue: {
200
+ maxReceiveCount: 5,
201
+ queue: this.deadLetterQueue,
202
+ },
203
+ });
204
+ // Create SNS topic
205
+ this.topic = new aws_cdk_lib_1.aws_sns.Topic(this, "Topic", {
206
+ displayName: `${id}-StacLoaderTopic`,
207
+ });
208
+ // Subscribe the queue to the topic
209
+ this.topic.addSubscription(new aws_cdk_lib_1.aws_sns_subscriptions.SqsSubscription(this.queue));
210
+ // Create the lambda function
211
+ this.lambdaFunction = new aws_cdk_lib_1.aws_lambda.Function(this, "Function", {
212
+ runtime: lambdaRuntime,
213
+ handler: "stac_loader.handler.handler",
214
+ vpc: props.vpc,
215
+ vpcSubnets: props.subnetSelection,
216
+ code: aws_cdk_lib_1.aws_lambda.Code.fromDockerBuild(path.join(__dirname, ".."), {
217
+ file: "stac-loader/runtime/Dockerfile",
218
+ platform: "linux/amd64",
219
+ buildArgs: {
220
+ PYTHON_VERSION: lambdaRuntime.toString().replace("python", ""),
221
+ PGSTAC_VERSION: props.pgstacDb.pgstacVersion,
222
+ },
223
+ }),
224
+ memorySize: props.memorySize ?? 1024,
225
+ timeout: aws_cdk_lib_1.Duration.seconds(timeoutSeconds),
226
+ reservedConcurrentExecutions: maxConcurrency,
227
+ logRetention: aws_cdk_lib_1.aws_logs.RetentionDays.ONE_WEEK,
228
+ environment: {
229
+ PGSTAC_SECRET_ARN: props.pgstacDb.pgstacSecret.secretArn,
230
+ ...props.environment,
231
+ },
232
+ // overwrites defaults with user-provided configurable properties
233
+ ...props.lambdaFunctionOptions,
234
+ });
235
+ // Grant permissions to read the database secret
236
+ props.pgstacDb.pgstacSecret.grantRead(this.lambdaFunction);
237
+ // Add SQS event source to the lambda
238
+ this.lambdaFunction.addEventSource(new aws_cdk_lib_1.aws_lambda_event_sources.SqsEventSource(this.queue, {
239
+ batchSize: props.batchSize ?? 500,
240
+ maxBatchingWindow: aws_cdk_lib_1.Duration.minutes(props.maxBatchingWindowMinutes ?? 1),
241
+ maxConcurrency: maxConcurrency,
242
+ reportBatchItemFailures: true,
243
+ }));
244
+ // Create outputs
245
+ const exportPrefix = aws_cdk_lib_1.Stack.of(this).stackName;
246
+ new aws_cdk_lib_1.CfnOutput(this, "TopicArn", {
247
+ value: this.topic.topicArn,
248
+ description: "ARN of the StacLoader SNS Topic",
249
+ exportName: `${exportPrefix}-stac-loader-topic-arn`,
250
+ });
251
+ new aws_cdk_lib_1.CfnOutput(this, "QueueUrl", {
252
+ value: this.queue.queueUrl,
253
+ description: "URL of the StacLoader SQS Queue",
254
+ exportName: `${exportPrefix}-stac-loader-queue-url`,
255
+ });
256
+ new aws_cdk_lib_1.CfnOutput(this, "DeadLetterQueueUrl", {
257
+ value: this.deadLetterQueue.queueUrl,
258
+ description: "URL of the StacLoader Dead Letter Queue",
259
+ exportName: `${exportPrefix}-stac-loader-deadletter-queue-url`,
260
+ });
261
+ new aws_cdk_lib_1.CfnOutput(this, "FunctionName", {
262
+ value: this.lambdaFunction.functionName,
263
+ description: "Name of the StacLoader Lambda Function",
264
+ exportName: `${exportPrefix}-stac-loader-function-name`,
265
+ });
266
+ }
267
+ }
268
+ exports.StacLoader = StacLoader;
269
+ _a = JSII_RTTI_SYMBOL_1;
270
+ StacLoader[_a] = { fqn: "eoapi-cdk.StacLoader", version: "8.3.1" };
271
+ /**
272
+ * @deprecated Use StacLoader instead. StacItemLoader will be removed in a future version.
273
+ */
274
+ class StacItemLoader extends StacLoader {
275
+ constructor(scope, id, props) {
276
+ console.warn(`StacItemLoader is deprecated. Please use StacLoader instead. ` +
277
+ `StacItemLoader will be removed in a future version.`);
278
+ super(scope, id, props);
279
+ }
280
+ }
281
+ exports.StacItemLoader = StacItemLoader;
282
+ _b = JSII_RTTI_SYMBOL_1;
283
+ StacItemLoader[_b] = { fqn: "eoapi-cdk.StacItemLoader", version: "8.3.1" };
284
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7OztBQUFBLDZDQVdxQjtBQUNyQiwyQ0FBdUM7QUFFdkMsNkJBQTZCO0FBMEk3Qjs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQStLRztBQUNILE1BQWEsVUFBVyxTQUFRLHNCQUFTO0lBb0R2QyxZQUFZLEtBQWdCLEVBQUUsRUFBVSxFQUFFLEtBQXNCO1FBQzlELEtBQUssQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLENBQUM7UUFFakIsTUFBTSxjQUFjLEdBQUcsS0FBSyxDQUFDLG9CQUFvQixJQUFJLEdBQUcsQ0FBQztRQUN6RCxNQUFNLGFBQWEsR0FBRyxLQUFLLENBQUMsYUFBYSxJQUFJLHdCQUFNLENBQUMsT0FBTyxDQUFDLFdBQVcsQ0FBQztRQUN4RSxNQUFNLGNBQWMsR0FBRyxLQUFLLENBQUMsY0FBYyxJQUFJLENBQUMsQ0FBQztRQUVqRCwyQkFBMkI7UUFDM0IsSUFBSSxDQUFDLGVBQWUsR0FBRyxJQUFJLHFCQUFHLENBQUMsS0FBSyxDQUFDLElBQUksRUFBRSxpQkFBaUIsRUFBRTtZQUM1RCxlQUFlLEVBQUUsc0JBQVEsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDO1NBQ25DLENBQUMsQ0FBQztRQUVILG9CQUFvQjtRQUNwQixJQUFJLENBQUMsS0FBSyxHQUFHLElBQUkscUJBQUcsQ0FBQyxLQUFLLENBQUMsSUFBSSxFQUFFLE9BQU8sRUFBRTtZQUN4QyxpQkFBaUIsRUFBRSxzQkFBUSxDQUFDLE9BQU8sQ0FBQyxjQUFjLEdBQUcsRUFBRSxDQUFDO1lB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QUUsSUFBSSxDQUFDLEtBQUssQ0FBQyxRQUFRO1lBQzFCLFdBQVcsRUFBRSxpQ0FBaUM7WUFDOUMsVUFBVSxFQUFFLEdBQUcsWUFBWSx3QkFBd0I7U0FDcEQsQ0FBQyxDQUFDO1FBRUgsSUFBSSx1QkFBUyxDQUFDLElBQUksRUFBRSxvQkFBb0IsRUFBRTtZQUN4QyxLQUFLLEVBQUUsSUFBSSxDQUFDLGVBQWUsQ0FBQyxRQUFRO1lBQ3BDLFdBQVcsRUFBRSx5Q0FBeUM7WUFDdEQsVUFBVSxFQUFFLEdBQUcsWUFBWSxtQ0FBbUM7U0FDL0QsQ0FBQyxDQUFDO1FBRUgsSUFBSSx1QkFBUyxDQUFDLElBQUksRUFBRSxjQUFjLEVBQUU7WUFDbEMsS0FBSyxFQUFFLElBQUksQ0FBQyxjQUFjLENBQUMsWUFBWTtZQUN2QyxXQUFXLEVBQUUsd0NBQXdDO1lBQ3JELFVBQVUsRUFBRSxHQUFHLFlBQVksNEJBQTRCO1NBQ3hELENBQUMsQ0FBQztJQUNMLENBQUM7O0FBdEpILGdDQXVKQzs7O0FBRUQ7O0dBRUc7QUFDSCxNQUFhLGNBQWUsU0FBUSxVQUFVO0lBQzVDLFlBQVksS0FBZ0IsRUFBRSxFQUFVLEVBQUUsS0FBc0I7UUFDOUQsT0FBTyxDQUFDLElBQUksQ0FDViwrREFBK0Q7WUFDN0QscURBQXFELENBQ3hELENBQUM7UUFFRixLQUFLLENBQUMsS0FBSyxFQUFFLEVBQUUsRUFBRSxLQUFLLENBQUMsQ0FBQztJQUMxQixDQUFDOztBQVJILHdDQVNDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHtcbiAgYXdzX2VjMiBhcyBlYzIsXG4gIGF3c19sYW1iZGEgYXMgbGFtYmRhLFxuICBhd3Nfc3FzIGFzIHNxcyxcbiAgYXdzX3NucyBhcyBzbnMsXG4gIGF3c19zbnNfc3Vic2NyaXB0aW9ucyBhcyBzbnNTdWJzY3JpcHRpb25zLFxuICBhd3NfbGFtYmRhX2V2ZW50X3NvdXJjZXMgYXMgbGFtYmRhRXZlbnRTb3VyY2VzLFxuICBhd3NfbG9ncyBhcyBsb2dzLFxuICBEdXJhdGlvbixcbiAgQ2ZuT3V0cHV0LFxuICBTdGFjayxcbn0gZnJvbSBcImF3cy1jZGstbGliXCI7XG5pbXBvcnQgeyBDb25zdHJ1Y3QgfSBmcm9tIFwiY29uc3RydWN0c1wiO1xuaW1wb3J0IHsgUGdTdGFjRGF0YWJhc2UgfSBmcm9tIFwiLi4vZGF0YWJhc2VcIjtcbmltcG9ydCAqIGFzIHBhdGggZnJvbSBcInBhdGhcIjtcbmltcG9ydCB7IEN1c3RvbUxhbWJkYUZ1bmN0aW9uUHJvcHMgfSBmcm9tIFwiLi4vdXRpbHNcIjtcblxuLyoqXG4gKiBDb25maWd1cmF0aW9uIHByb3BlcnRpZXMgZm9yIHRoZSBTdGFjTG9hZGVyIGNvbnN0cnVjdC5cbiAqXG4gKiBUaGUgU3RhY0xvYWRlciBpcyBwYXJ0IG9mIGEgdHdvLXBoYXNlIHNlcnZlcmxlc3MgU1RBQyBpbmdlc3Rpb24gcGlwZWxpbmVcbiAqIHRoYXQgbG9hZHMgU1RBQyBjb2xsZWN0aW9ucyBhbmQgaXRlbXMgaW50byBhIHBnc3RhYyBkYXRhYmFzZS4gVGhpcyBjb25zdHJ1Y3QgY3JlYXRlc1xuICogdGhlIGluZnJhc3RydWN0dXJlIGZvciByZWNlaXZpbmcgU1RBQyBvYmplY3RzIGZyb20gbXVsdGlwbGUgc291cmNlczpcbiAqIDEuIFNOUyBtZXNzYWdlcyBjb250YWluaW5nIFNUQUMgbWV0YWRhdGEgKGRpcmVjdCBpbmdlc3Rpb24pXG4gKiAyLiBTMyBldmVudCBub3RpZmljYXRpb25zIGZvciBTVEFDIG9iamVjdHMgdXBsb2FkZWQgdG8gUzMgYnVja2V0c1xuICpcbiAqIE9iamVjdHMgZnJvbSBib3RoIHNvdXJjZXMgYXJlIGJhdGNoZWQgYW5kIGluc2VydGVkIGludG8gUG9zdGdyZVNRTCB3aXRoIHRoZSBwZ3N0YWMgZXh0ZW5zaW9uLlxuICpcbiAqIEBleGFtcGxlXG4gKiBjb25zdCBsb2FkZXIgPSBuZXcgU3RhY0xvYWRlcih0aGlzLCAnU3RhY0xvYWRlcicsIHtcbiAqICAgcGdzdGFjRGI6IGRhdGFiYXNlLFxuICogICBiYXRjaFNpemU6IDEwMDAsXG4gKiAgIG1heEJhdGNoaW5nV2luZG93TWludXRlczogMSxcbiAqICAgbGFtYmRhVGltZW91dFNlY29uZHM6IDMwMFxuICogfSk7XG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgU3RhY0xvYWRlclByb3BzIHtcbiAgLyoqXG4gICAqIFRoZSBQZ1NUQUMgZGF0YWJhc2UgaW5zdGFuY2UgdG8gbG9hZCBkYXRhIGludG8uXG4gICAqXG4gICAqIFRoaXMgZGF0YWJhc2UgbXVzdCBoYXZlIHRoZSBwZ3N0YWMgZXh0ZW5zaW9uIGluc3RhbGxlZCBhbmQgYmUgcHJvcGVybHlcbiAgICogY29uZmlndXJlZCB3aXRoIGNvbGxlY3Rpb25zIGJlZm9yZSBvYmplY3RzIGNhbiBiZSBsb2FkZWQuIFRoZSBsb2FkZXIgd2lsbFxuICAgKiB1c2UgQVdTIFNlY3JldHMgTWFuYWdlciB0byBzZWN1cmVseSBhY2Nlc3MgZGF0YWJhc2UgY3JlZGVudGlhbHMuXG4gICAqL1xuICByZWFkb25seSBwZ3N0YWNEYjogUGdTdGFjRGF0YWJhc2U7XG5cbiAgLyoqXG4gICAqIFZQQyBpbnRvIHdoaWNoIHRoZSBsYW1iZGEgc2hvdWxkIGJlIGRlcGxveWVkLlxuICAgKi9cbiAgcmVhZG9ubHkgdnBjPzogZWMyLklWcGM7XG5cbiAgLyoqXG4gICAqIFN1Ym5ldCBpbnRvIHdoaWNoIHRoZSBsYW1iZGEgc2hvdWxkIGJlIGRlcGxveWVkLlxuICAgKi9cbiAgcmVhZG9ubHkgc3VibmV0U2VsZWN0aW9uPzogZWMyLlN1Ym5ldFNlbGVjdGlvbjtcblxuICAvKipcbiAgICogVGhlIGxhbWJkYSBydW50aW1lIHRvIHVzZSBmb3IgdGhlIGl0ZW0gbG9hZGluZyBmdW5jdGlvbi5cbiAgICpcbiAgICogVGhlIGZ1bmN0aW9uIGlzIGltcGxlbWVudGVkIGluIFB5dGhvbiBhbmQgdXNlcyBweXBnc3RhYyBmb3IgZGF0YWJhc2VcbiAgICogb3BlcmF0aW9ucy4gRW5zdXJlIHRoZSBydW50aW1lIHZlcnNpb24gaXMgY29tcGF0aWJsZSB3aXRoIHRoZSBwZ3N0YWNcbiAgICogdmVyc2lvbiBzcGVjaWZpZWQgaW4gdGhlIGRhdGFiYXNlIGNvbmZpZ3VyYXRpb24uXG4gICAqXG4gICAqIEBkZWZhdWx0IGxhbWJkYS5SdW50aW1lLlBZVEhPTl8zXzExXG4gICAqL1xuICByZWFkb25seSBsYW1iZGFSdW50aW1lPzogbGFtYmRhLlJ1bnRpbWU7XG5cbiAgLyoqXG4gICAqIFRoZSB0aW1lb3V0IGZvciB0aGUgaXRlbSBsb2FkIGxhbWJkYSBpbiBzZWNvbmRzLlxuICAgKlxuICAgKiBUaGlzIHNob3VsZCBhY2NvbW1vZGF0ZSB0aGUgdGltZSBuZWVkZWQgdG8gcHJvY2VzcyB1cCB0byBgYmF0Y2hTaXplYFxuICAgKiBvYmplY3RzIGFuZCBwZXJmb3JtIGRhdGFiYXNlIGluc2VydGlvbnMuIFRoZSBTUVMgdmlzaWJpbGl0eSB0aW1lb3V0XG4gICAqIHdpbGwgYmUgc2V0IHRvIHRoaXMgdmFsdWUgcGx1cyAxMCBzZWNvbmRzLlxuICAgKlxuICAgKiBAZGVmYXVsdCAzMDBcbiAgICovXG4gIHJlYWRvbmx5IGxhbWJkYVRpbWVvdXRTZWNvbmRzPzogbnVtYmVyO1xuXG4gIC8qKlxuICAgKiBNZW1vcnkgc2l6ZSBmb3IgdGhlIGxhbWJkYSBmdW5jdGlvbiBpbiBNQi5cbiAgICpcbiAgICogSGlnaGVyIG1lbW9yeSBhbGxvY2F0aW9uIG1heSBpbXByb3ZlIHBlcmZvcm1hbmNlIHdoZW4gcHJvY2Vzc2luZ1xuICAgKiBsYXJnZSBiYXRjaGVzIG9mIFNUQUMgb2JqZWN0cywgZXNwZWNpYWxseSBmb3IgbWVtb3J5LWludGVuc2l2ZVxuICAgKiBkYXRhYmFzZSBvcGVyYXRpb25zLlxuICAgKlxuICAgKiBAZGVmYXVsdCAxMDI0XG4gICAqL1xuICByZWFkb25seSBtZW1vcnlTaXplPzogbnVtYmVyO1xuXG4gIC8qKlxuICAgKiBTUVMgYmF0Y2ggc2l6ZSBmb3IgbGFtYmRhIGV2ZW50IHNvdXJjZS5cbiAgICpcbiAgICogVGhpcyBkZXRlcm1pbmVzIHRoZSBtYXhpbXVtIG51bWJlciBvZiBTVEFDIG9iamVjdHMgdGhhdCB3aWxsIGJlXG4gICAqIHByb2Nlc3NlZCB0b2dldGhlciBpbiBhIHNpbmdsZSBsYW1iZGEgaW52b2NhdGlvbi4gTGFyZ2VyIGJhdGNoXG4gICAqIHNpemVzIGltcHJvdmUgZGF0YWJhc2UgaW5zZXJ0aW9uIGVmZmljaWVuY3kgYnV0IHJlcXVpcmUgbW9yZVxuICAgKiBtZW1vcnkgYW5kIGxvbmdlciBwcm9jZXNzaW5nIHRpbWUuXG4gICAqXG4gICAqICoqQmF0Y2hpbmcgQmVoYXZpb3IqKjogU1FTIHdpbGwgd2FpdCB0byBhY2N1bXVsYXRlIHVwIHRvIHRoaXMgbWFueVxuICAgKiBtZXNzYWdlcyBiZWZvcmUgdHJpZ2dlcmluZyB0aGUgTGFtYmRhLCBPUiB1bnRpbCB0aGUgbWF4QmF0Y2hpbmdXaW5kb3dcbiAgICogdGltZW91dCBpcyByZWFjaGVkLCB3aGljaGV2ZXIgY29tZXMgZmlyc3QuIFRoaXMgY3JlYXRlcyBhbiBlZmZpY2llbnRcbiAgICogYmFsYW5jZSBiZXR3ZWVuIHRocm91Z2hwdXQgYW5kIGxhdGVuY3kuXG4gICAqXG4gICAqIEBkZWZhdWx0IDUwMFxuICAgKi9cbiAgcmVhZG9ubHkgYmF0Y2hTaXplPzogbnVtYmVyO1xuXG4gIC8qKlxuICAgKiBNYXhpbXVtIGJhdGNoaW5nIHdpbmRvdyBpbiBtaW51dGVzLlxuICAgKlxuICAgKiBFdmVuIGlmIHRoZSBiYXRjaCBzaXplIGlzbid0IHJlYWNoZWQsIHRoZSBsYW1iZGEgd2lsbCBiZSB0cmlnZ2VyZWRcbiAgICogYWZ0ZXIgdGhpcyB0aW1lIHBlcmlvZCB0byBlbnN1cmUgdGltZWx5IHByb2Nlc3Npbmcgb2Ygb2JqZWN0cy5cbiAgICogVGhpcyBwcmV2ZW50cyBvYmplY3RzIGZyb20gd2FpdGluZyBpbmRlZmluaXRlbHkgaW4gbG93LXZvbHVtZSBzY2VuYXJpb3MuXG4gICAqXG4gICAqICoqSW1wb3J0YW50Kio6IFRoaXMgdGltZW91dCB3b3JrcyBpbiBjb25qdW5jdGlvbiB3aXRoIGJhdGNoU2l6ZSAtIFNRU1xuICAgKiB3aWxsIHRyaWdnZXIgdGhlIExhbWJkYSB3aGVuIEVJVEhFUiB0aGUgYmF0Y2ggc2l6ZSBpcyByZWFjaGVkIE9SIHRoaXNcbiAgICogdGltZSB3aW5kb3cgZXhwaXJlcywgZW5zdXJpbmcgb2JqZWN0cyBhcmUgcHJvY2Vzc2VkIGluIGEgdGltZWx5IG1hbm5lclxuICAgKiByZWdhcmRsZXNzIG9mIHZvbHVtZS5cbiAgICpcbiAgICogQGRlZmF1bHQgMVxuICAgKi9cbiAgcmVhZG9ubHkgbWF4QmF0Y2hpbmdXaW5kb3dNaW51dGVzPzogbnVtYmVyO1xuXG4gIC8qKlxuICAgKiBNYXhpbXVtIGNvbmN1cnJlbnQgZXhlY3V0aW9ucyBmb3IgdGhlIFN0YWNMb2FkZXIgTGFtYmRhIGZ1bmN0aW9uXG4gICAqXG4gICAqIFRoaXMgbGltaXQgd2lsbCBiZSBhcHBsaWVkIHRvIHRoZSBMYW1iZGEgZnVuY3Rpb24gYW5kIHdpbGwgY29udHJvbCBob3dcbiAgICogbWFueSBjb25jdXJyZW50IGJhdGNoZXMgd2lsbCBiZSByZWxlYXNlZCBmcm9tIHRoZSBTUVMgcXVldWUuXG4gICAqXG4gICAqIEBkZWZhdWx0IDJcbiAgICovXG4gIHJlYWRvbmx5IG1heENvbmN1cnJlbmN5PzogbnVtYmVyO1xuXG4gIC8qKlxuICAgKiBBZGRpdGlvbmFsIGVudmlyb25tZW50IHZhcmlhYmxlcyBmb3IgdGhlIGxhbWJkYSBmdW5jdGlvbi5cbiAgICpcbiAgICogVGhlc2Ugd2lsbCBiZSBtZXJnZWQgd2l0aCB0aGUgZGVmYXVsdCBlbnZpcm9ubWVudCB2YXJpYWJsZXMgaW5jbHVkaW5nXG4gICAqIFBHU1RBQ19TRUNSRVRfQVJOLiBVc2UgdGhpcyBmb3IgY3VzdG9tIGNvbmZpZ3VyYXRpb24gb3IgZGVidWdnaW5nIGZsYWdzLlxuICAgKlxuICAgKiBJZiB5b3Ugd2FudCB0byBlbmFibGUgdGhlIG9wdGlvbiB0byB1cGxvYWQgYSBib2lsZXJwbGF0ZSBjb2xsZWN0aW9uIHJlY29yZFxuICAgKiBpbiB0aGUgZXZlbnQgdGhhdCB0aGUgY29sbGVjdGlvbiByZWNvcmQgZG9lcyBub3QgeWV0IGV4aXN0IGZvciBhbiBpdGVtIHRoYXRcbiAgICogaXMgc2V0IHRvIGJlIGxvYWRlZCwgc2V0IHRoZSB2YXJpYWJsZSBgXCJDUkVBVEVfQ09MTEVDVElPTlNfSUZfTUlTU0lOR1wiOiBcIlRSVUVcImAuXG4gICAqL1xuICByZWFkb25seSBlbnZpcm9ubWVudD86IHsgW2tleTogc3RyaW5nXTogc3RyaW5nIH07XG5cbiAgLyoqXG4gICAqIENhbiBiZSB1c2VkIHRvIG92ZXJyaWRlIHRoZSBkZWZhdWx0IGxhbWJkYSBmdW5jdGlvbiBwcm9wZXJ0aWVzLlxuICAgKlxuICAgKiBAZGVmYXVsdCAtIGRlZmluZWQgaW4gdGhlIGNvbnN0cnVjdC5cbiAgICovXG4gIHJlYWRvbmx5IGxhbWJkYUZ1bmN0aW9uT3B0aW9ucz86IEN1c3RvbUxhbWJkYUZ1bmN0aW9uUHJvcHM7XG59XG5cbi8qKlxuICogQVdTIENESyBDb25zdHJ1Y3QgZm9yIFNUQUMgT2JqZWN0IExvYWRpbmcgSW5mcmFzdHJ1Y3R1cmVcbiAqXG4gKiBUaGUgU3RhY0xvYWRlciBjcmVhdGVzIGEgc2VydmVybGVzcywgZXZlbnQtZHJpdmVuIHN5c3RlbSBmb3IgbG9hZGluZ1xuICogU1RBQyAoU3BhdGlvVGVtcG9yYWwgQXNzZXQgQ2F0YWxvZykgb2JqZWN0cyBpbnRvIGEgUG9zdGdyZVNRTCBkYXRhYmFzZSB3aXRoXG4gKiB0aGUgcGdzdGFjIGV4dGVuc2lvbi4gVGhpcyBjb25zdHJ1Y3Qgc3VwcG9ydHMgbXVsdGlwbGUgaW5nZXN0aW9uIHBhdGh3YXlzXG4gKiBmb3IgZmxleGlibGUgU1RBQyBvYmplY3QgbG9hZGluZy5cbiAqXG4gKiAjIyBBcmNoaXRlY3R1cmUgT3ZlcnZpZXdcbiAqXG4gKiBUaGlzIGNvbnN0cnVjdCBjcmVhdGVzIHRoZSBmb2xsb3dpbmcgQVdTIHJlc291cmNlczpcbiAqIC0gKipTTlMgVG9waWMqKjogRW50cnkgcG9pbnQgZm9yIFNUQUMgb2JqZWN0cyBhbmQgUzMgZXZlbnQgbm90aWZpY2F0aW9uc1xuICogLSAqKlNRUyBRdWV1ZSoqOiBCdWZmZXJzIGFuZCBiYXRjaGVzIG1lc3NhZ2VzIGJlZm9yZSBwcm9jZXNzaW5nICg2MC1zZWNvbmQgdmlzaWJpbGl0eSB0aW1lb3V0KVxuICogLSAqKkRlYWQgTGV0dGVyIFF1ZXVlKio6IENhcHR1cmVzIGZhaWxlZCBsb2FkaW5nIGF0dGVtcHRzIGFmdGVyIDUgcmV0cmllc1xuICogLSAqKkxhbWJkYSBGdW5jdGlvbioqOiBQeXRob24gZnVuY3Rpb24gdGhhdCBwcm9jZXNzZXMgYmF0Y2hlcyBhbmQgaW5zZXJ0cyBvYmplY3RzIGludG8gcGdzdGFjXG4gKlxuICogIyMgRGF0YSBGbG93XG4gKlxuICogVGhlIGxvYWRlciBzdXBwb3J0cyB0d28gcHJpbWFyeSBkYXRhIGluZ2VzdGlvbiBwYXR0ZXJuczpcbiAqXG4gKiAjIyMgRGlyZWN0IFNUQUMgT2JqZWN0IFB1Ymxpc2hpbmdcbiAqIDEuIFNUQUMgb2JqZWN0cyAoSlNPTikgYXJlIHB1Ymxpc2hlZCBkaXJlY3RseSB0byB0aGUgU05TIHRvcGljIGluIG1lc3NhZ2UgYm9kaWVzXG4gKiAyLiBUaGUgU1FTIHF1ZXVlIGNvbGxlY3RzIG1lc3NhZ2VzIGFuZCBiYXRjaGVzIHRoZW0gKHVwIHRvIHtiYXRjaFNpemV9IG9iamVjdHMgb3IgMSBtaW51dGUgd2luZG93KVxuICogMy4gVGhlIExhbWJkYSBmdW5jdGlvbiByZWNlaXZlcyBiYXRjaGVzLCB2YWxpZGF0ZXMgb2JqZWN0cywgYW5kIGluc2VydHMgaW50byBwZ3N0YWNcbiAqXG4gKiAjIyMgUzMgRXZlbnQtRHJpdmVuIExvYWRpbmdcbiAqIDEuIEFuIFMzIGJ1Y2tldCBpcyBjb25maWd1cmVkIHRvIHNlbmQgbm90aWZpY2F0aW9ucyB0byB0aGUgU05TIHRvcGljIHdoZW4ganNvbiBmaWxlcyBhcmUgY3JlYXRlZFxuICogMi4gU1RBQyBvYmplY3RzIGFyZSB1cGxvYWRlZCB0byBTMyBidWNrZXRzIGFzIEpTT04vR2VvSlNPTiBmaWxlc1xuICogMy4gUzMgZXZlbnQgbm90aWZpY2F0aW9ucyBhcmUgc2VudCB0byB0aGUgU05TIHRvcGljIHdoZW4gb2JqZWN0cyBhcmUgdXBsb2FkZWRcbiAqIDQuIFRoZSBMYW1iZGEgZnVuY3Rpb24gcmVjZWl2ZXMgUzMgZXZlbnRzIGluIHRoZSBTUVMgbWVzc2FnZSBiYXRjaCwgZmV0Y2hlcyBvYmplY3RzIGZyb20gUzMsIGFuZCBsb2FkcyBpbnRvIHBnc3RhY1xuICpcbiAqICMjIEJhdGNoaW5nIEJlaGF2aW9yXG4gKlxuICogVGhlIFNRUy10by1MYW1iZGEgaW50ZWdyYXRpb24gdXNlcyBpbnRlbGxpZ2VudCBiYXRjaGluZyB0byBvcHRpbWl6ZSBwZXJmb3JtYW5jZTpcbiAqXG4gKiAtICoqQmF0Y2ggU2l6ZSoqOiBMYW1iZGEgd2FpdHMgdG8gcmVjZWl2ZSB1cCB0byBgYmF0Y2hTaXplYCBtZXNzYWdlcyAoZGVmYXVsdDogNTAwKVxuICogLSAqKkJhdGNoaW5nIFdpbmRvdyoqOiBJZiBmZXdlciB0aGFuIGBiYXRjaFNpemVgIG1lc3NhZ2VzIGFyZSBhdmFpbGFibGUsIExhbWJkYVxuICogICB0cmlnZ2VycyBhZnRlciBgbWF4QmF0Y2hpbmdXaW5kb3dgIG1pbnV0ZXMgKGRlZmF1bHQ6IDEgbWludXRlKVxuICogLSAqKlRyaWdnZXIgQ29uZGl0aW9uKio6IExhbWJkYSBleGVjdXRlcyB3aGVuIEVJVEhFUiBjb25kaXRpb24gaXMgbWV0IGZpcnN0XG4gKiAtICoqQ29uY3VycmVuY3kqKjogTGltaXRlZCB0byBgbWF4Q29uY3VycmVuY3lgIGNvbmN1cnJlbnQgZXhlY3V0aW9ucyB0byBwcmV2ZW50IGRhdGFiYXNlIG92ZXJsb2FkXG4gKiAtICoqUGFydGlhbCBGYWlsdXJlcyoqOiBVc2VzIGByZXBvcnRCYXRjaEl0ZW1GYWlsdXJlc2AgdG8gcmV0cnkgb25seSBmYWlsZWQgb2JqZWN0c1xuICpcbiAqIFRoaXMgYXBwcm9hY2ggYmFsYW5jZXMgdGhyb3VnaHB1dCAobGFyZ2VyIGJhdGNoZXMgPSBmZXdlciBkYXRhYmFzZSBjb25uZWN0aW9ucylcbiAqIHdpdGggbGF0ZW5jeSAodGltZS1iYXNlZCB0cmlnZ2VycyBwcmV2ZW50IGluZGVmaW5pdGUgd2FpdGluZykuXG4gKlxuICogIyMgRXJyb3IgSGFuZGxpbmcgYW5kIERlYWQgTGV0dGVyIFF1ZXVlXG4gKlxuICogRmFpbGVkIG1lc3NhZ2VzIGFyZSBzZW50IHRvIHRoZSBkZWFkIGxldHRlciBxdWV1ZSBhZnRlciA1IHByb2Nlc3NpbmcgYXR0ZW1wdHMuXG4gKiAqKkltcG9ydGFudCoqOiBUaGlzIGNvbnN0cnVjdCBwcm92aWRlcyBOTyBhdXRvbWF0ZWQgaGFuZGxpbmcgb2YgZGVhZCBsZXR0ZXIgcXVldWVcbiAqIG1lc3NhZ2VzIC0gbW9uaXRvcmluZywgaW5zcGVjdGlvbiwgYW5kIHJlcHJvY2Vzc2luZyBvZiBmYWlsZWQgb2JqZWN0cyBpcyB0aGVcbiAqIHJlc3BvbnNpYmlsaXR5IG9mIHRoZSBpbXBsZW1lbnRpbmcgYXBwbGljYXRpb24uXG4gKlxuICogQ29uc2lkZXIgaW1wbGVtZW50aW5nOlxuICogLSBDbG91ZFdhdGNoIGFsYXJtcyBvbiBkZWFkIGxldHRlciBxdWV1ZSBkZXB0aFxuICogLSBNYW51YWwgb3IgYXV0b21hdGVkIHJlcHJvY2Vzc2luZyB3b3JrZmxvd3NcbiAqIC0gTG9nZ2luZyBhbmQgYWxlcnRpbmcgZm9yIGZhaWxlZCBvYmplY3RzXG4gKiAtIFJlZ3VsYXIgY2xlYW51cCBvZiBvbGQgZGVhZCBsZXR0ZXIgbWVzc2FnZXMgKDE0LWRheSByZXRlbnRpb24pXG4gKlxuICogIyMgT3BlcmF0aW9uYWwgQ2hhcmFjdGVyaXN0aWNzXG4gKlxuICogLSAqKlNjYWxhYmlsaXR5Kio6IExhbWJkYSBzY2FsZXMgYXV0b21hdGljYWxseSBiYXNlZCBvbiBxdWV1ZSBkZXB0aFxuICogLSAqKlJlbGlhYmlsaXR5Kio6IERlYWQgbGV0dGVyIHF1ZXVlIGNhcHR1cmVzIGZhaWx1cmVzIGZvciBkZWJ1Z2dpbmdcbiAqIC0gKipFZmZpY2llbmN5Kio6IEJhdGNoaW5nIG9wdGltaXplcyBkYXRhYmFzZSBvcGVyYXRpb25zIGZvciBoaWdoIHRocm91Z2hwdXRcbiAqIC0gKipTZWN1cml0eSoqOiBEYXRhYmFzZSBjcmVkZW50aWFscyBhY2Nlc3NlZCB2aWEgQVdTIFNlY3JldHMgTWFuYWdlclxuICogLSAqKk9ic2VydmFiaWxpdHkqKjogQ2xvdWRXYXRjaCBsb2dzIHJldGFpbmVkIGZvciBvbmUgd2Vla1xuICpcbiAqICMjIFByZXJlcXVpc2l0ZXNcbiAqXG4gKiBCZWZvcmUgdXNpbmcgdGhpcyBjb25zdHJ1Y3QsIGVuc3VyZTpcbiAqIC0gVGhlIHBnc3RhYyBkYXRhYmFzZSBoYXMgY29sbGVjdGlvbnMgbG9hZGVkIChvYmplY3RzIHJlcXVpcmUgZXhpc3RpbmcgY29sbGVjdGlvbiBJRHMpXG4gKiAtIERhdGFiYXNlIGNyZWRlbnRpYWxzIGFyZSBzdG9yZWQgaW4gQVdTIFNlY3JldHMgTWFuYWdlclxuICogLSBUaGUgcGdzdGFjIGV4dGVuc2lvbiBpcyBwcm9wZXJseSBpbnN0YWxsZWQgYW5kIGNvbmZpZ3VyZWRcbiAqXG4gKiAjIyBVc2FnZSBFeGFtcGxlXG4gKlxuICogYGBgdHlwZXNjcmlwdFxuICogLy8gQ3JlYXRlIGRhdGFiYXNlIGZpcnN0XG4gKiBjb25zdCBkYXRhYmFzZSA9IG5ldyBQZ1N0YWNEYXRhYmFzZSh0aGlzLCAnRGF0YWJhc2UnLCB7XG4gKiAgIHBnc3RhY1ZlcnNpb246ICcwLjkuNSdcbiAqIH0pO1xuICpcbiAqIC8vIENyZWF0ZSBPYmplY3QgbG9hZGVyXG4gKiBjb25zdCBsb2FkZXIgPSBuZXcgU3RhY0xvYWRlcih0aGlzLCAnU3RhY0xvYWRlcicsIHtcbiAqICAgcGdzdGFjRGI6IGRhdGFiYXNlLFxuICogICBiYXRjaFNpemU6IDEwMDAsICAgICAgICAgIC8vIFByb2Nlc3MgdXAgdG8gMTAwMCBvYmplY3RzIHBlciBiYXRjaFxuICogICBtYXhCYXRjaGluZ1dpbmRvd01pbnV0ZXM6IDEsIC8vIFdhaXQgbWF4IDEgbWludXRlIHRvIGZpbGwgYmF0Y2hcbiAqICAgbGFtYmRhVGltZW91dFNlY29uZHM6IDMwMCAgICAgLy8gQWxsb3cgdXAgdG8gMzAwIHNlY29uZHMgZm9yIGRhdGFiYXNlIG9wZXJhdGlvbnNcbiAqIH0pO1xuICpcbiAqIC8vIFRoZSB0b3BpYyBBUk4gY2FuIGJlIHVzZWQgYnkgb3RoZXIgc2VydmljZXMgdG8gcHVibGlzaCBvYmplY3RzXG4gKiBuZXcgQ2ZuT3V0cHV0KHRoaXMsICdMb2FkZXJUb3BpY0FybicsIHtcbiAqICAgdmFsdWU6IGxvYWRlci50b3BpYy50b3BpY0FyblxuICogfSk7XG4gKiBgYGBcbiAqXG4gKiAjIyBEaXJlY3QgT2JqZWN0IFB1Ymxpc2hpbmdcbiAqXG4gKiBFeHRlcm5hbCBzZXJ2aWNlcyBjYW4gcHVibGlzaCBTVEFDIG9iamVjdHMgZGlyZWN0bHkgdG8gdGhlIHRvcGljOlxuICpcbiAqIGBgYGJhc2hcbiAqIGF3cyBzbnMgcHVibGlzaCAtLXRvcGljLWFybiAkU1RBQ19MT0FEX1RPUElDIC0tbWVzc2FnZSAgJ3tcbiAqICAgXCJpZFwiOiBcImV4YW1wbGUtY29sbGVjdGlvblwiLFxuICogICBcInR5cGVcIjogXCJDb2xsZWN0aW9uXCIsXG4gKiAgIFwidGl0bGVcIjogXCJFeGFtcGxlIENvbGxlY3Rpb25cIixcbiAqICAgXCJkZXNjcmlwdGlvblwiOiBcIkFuIGV4YW1wbGUgY29sbGVjdGlvblwiLFxuICogICBcImxpY2Vuc2VcIjogXCJwcm9wcmlldGFyeVwiLFxuICogICBcImV4dGVudFwiOiB7XG4gKiAgICAgICBcInNwYXRpYWxcIjoge1wiYmJveFwiOiBbWy0xODAsIC05MCwgMTgwLCA5MF1dfSxcbiAqICAgICAgIFwidGVtcG9yYWxcIjoge1wiaW50ZXJ2YWxcIjogW1tudWxsLCBudWxsXV19LFxuICogICB9LFxuICogICBcInN0YWNfdmVyc2lvblwiOiBcIjEuMS4wXCIsXG4gKiB9J1xuICpcbiAqIGF3cyBzbnMgcHVibGlzaCAtLXRvcGljLWFybiAkU1RBQ19MT0FEX1RPUElDIC0tbWVzc2FnZSAne1xuICogICBcInR5cGVcIjogXCJGZWF0dXJlXCIsXG4gKiAgIFwic3RhY192ZXJzaW9uXCI6IFwiMS4wLjBcIixcbiAqICAgXCJpZFwiOiBcImV4YW1wbGUtaXRlbVwiLFxuICogICBcInByb3BlcnRpZXNcIjoge1wiZGF0ZXRpbWVcIjogXCIyMDIxLTAxLTAxVDAwOjAwOjAwWlwifSxcbiAqICAgXCJnZW9tZXRyeVwiOiB7XCJ0eXBlXCI6IFwiUG9seWdvblwiLCBcImNvb3JkaW5hdGVzXCI6IFsuLi5dfSxcbiAqICAgXCJjb2xsZWN0aW9uXCI6IFwiZXhhbXBsZS1jb2xsZWN0aW9uXCJcbiAqIH0nXG4gKlxuICpcbiAqIGBgYFxuICpcbiAqICMjIFMzIEV2ZW50IENvbmZpZ3VyYXRpb25cbiAqXG4gKiBUbyBlbmFibGUgUzMgZXZlbnQtZHJpdmVuIGxvYWRpbmcsIGNvbmZpZ3VyZSBTMyBidWNrZXQgbm90aWZpY2F0aW9ucyB0byBzZW5kXG4gKiBldmVudHMgdG8gdGhlIFNOUyB0b3BpYyB3aGVuIFNUQUMgb2JqZWN0cyAoLmpzb24gb3IgLmdlb2pzb24gZmlsZXMpIGFyZSB1cGxvYWRlZDpcbiAqXG4gKiBgYGB0eXBlc2NyaXB0XG4gKiAvLyBDb25maWd1cmUgUzMgYnVja2V0IHRvIHNlbmQgbm90aWZpY2F0aW9ucyB0byB0aGUgbG9hZGVyIHRvcGljXG4gKiBidWNrZXQuYWRkRXZlbnROb3RpZmljYXRpb24oXG4gKiAgIHMzLkV2ZW50VHlwZS5PQkpFQ1RfQ1JFQVRFRCxcbiAqICAgbmV3IHMzbi5TbnNEZXN0aW5hdGlvbihsb2FkZXIudG9waWMpLFxuICogICB7IHN1ZmZpeDogJy5qc29uJyB9XG4gKiApO1xuICpcbiAqIGJ1Y2tldC5hZGRFdmVudE5vdGlmaWNhdGlvbihcbiAqICAgczMuRXZlbnRUeXBlLk9CSkVDVF9DUkVBVEVELFxuICogICBuZXcgczNuLlNuc0Rlc3RpbmF0aW9uKGxvYWRlci50b3BpYyksXG4gKiAgIHsgc3VmZml4OiAnLmdlb2pzb24nIH1cbiAqICk7XG4gKiBgYGBcbiAqXG4gKiBXaGVuIFNUQUMgb2JqZWN0cyBhcmUgdXBsb2FkZWQgdG8gdGhlIGNvbmZpZ3VyZWQgUzMgYnVja2V0LCB0aGUgbG9hZGVyIHdpbGw6XG4gKiAxLiBSZWNlaXZlIFMzIGV2ZW50IG5vdGlmaWNhdGlvbnMgdmlhIFNOU1xuICogMi4gRmV0Y2ggdGhlIFNUQUMgSlNPTiBmcm9tIFMzXG4gKiAzLiBWYWxpZGF0ZSBhbmQgbG9hZCB0aGUgb2JqZWN0cyBpbnRvIHRoZSBwZ3N0YWMgZGF0YWJhc2VcbiAqXG4gKiAjIyBNb25pdG9yaW5nIGFuZCBUcm91Ymxlc2hvb3RpbmdcbiAqXG4gKiAtIE1vbml0b3IgTGFtYmRhIGxvZ3M6IGAvYXdzL2xhbWJkYS97RnVuY3Rpb25OYW1lfWBcbiAqIC0gKipEZWFkIExldHRlciBRdWV1ZSoqOiBDaGVjayBmb3IgZmFpbGVkIG9iamVjdHMgLSAqKm5vIGF1dG9tYXRlZCBoYW5kbGluZyBwcm92aWRlZCoqXG4gKiAtIFVzZSBiYXRjaCBvYmplY3RzIGZhaWx1cmUgcmVwb3J0aW5nIGZvciBwYXJ0aWFsIGJhdGNoIHByb2Nlc3NpbmdcbiAqIC0gQ2xvdWRXYXRjaCBtZXRyaWNzIGF2YWlsYWJsZSBmb3IgcXVldWUgZGVwdGggYW5kIExhbWJkYSBwZXJmb3JtYW5jZVxuICpcbiAqICMjIyBEZWFkIExldHRlciBRdWV1ZSBNYW5hZ2VtZW50XG4gKlxuICogQXBwbGljYXRpb25zIG11c3QgaW1wbGVtZW50IHRoZWlyIG93biBkZWFkIGxldHRlciBxdWV1ZSBtb25pdG9yaW5nOlxuICpcbiAqIGBgYHR5cGVzY3JpcHRcbiAqIC8vIEV4YW1wbGU6IENsb3VkV2F0Y2ggYWxhcm0gZm9yIGRlYWQgbGV0dGVyIHF1ZXVlIGRlcHRoXG4gKiBuZXcgY2xvdWR3YXRjaC5BbGFybSh0aGlzLCAnRGVhZExldHRlckFsYXJtJywge1xuICogICBtZXRyaWM6IGxvYWRlci5kZWFkTGV0dGVyUXVldWUubWV0cmljQXBwcm94aW1hdGVOdW1iZXJPZlZpc2libGVNZXNzYWdlcygpLFxuICogICB0aHJlc2hvbGQ6IDEsXG4gKiAgIGV2YWx1YXRpb25QZXJpb2RzOiAxXG4gKiB9KTtcbiAqXG4gKiAvLyBFeGFtcGxlOiBMYW1iZGEgdG8gcmVwcm9jZXNzIGRlYWQgbGV0dGVyIG1lc3NhZ2VzXG4gKiBjb25zdCByZXByb2Nlc3NGdW5jdGlvbiA9IG5ldyBsYW1iZGEuRnVuY3Rpb24odGhpcywgJ1JlcHJvY2VzcycsIHtcbiAqICAgLy8gSW1wbGVtZW50YXRpb24gdG8gZmV0Y2ggYW5kIHJlcHVibGlzaCBmYWlsZWQgbWVzc2FnZXNcbiAqIH0pO1xuICogYGBgXG4gKlxuICovXG5leHBvcnQgY2xhc3MgU3RhY0xvYWRlciBleHRlbmRzIENvbnN0cnVjdCB7XG4gIC8qKlxuICAgKiBUaGUgU05TIHRvcGljIHRoYXQgcmVjZWl2ZXMgU1RBQyBvYmplY3RzIGFuZCBTMyBldmVudCBub3RpZmljYXRpb25zIGZvciBsb2FkaW5nLlxuICAgKlxuICAgKiBUaGlzIHRvcGljIHNlcnZlcyBhcyB0aGUgZW50cnkgcG9pbnQgZm9yIHR3byB0eXBlcyBvZiBldmVudHM6XG4gICAqIDEuIERpcmVjdCBTVEFDIEpTT04gZG9jdW1lbnRzIHB1Ymxpc2hlZCBieSBleHRlcm5hbCBzZXJ2aWNlc1xuICAgKiAyLiBTMyBldmVudCBub3RpZmljYXRpb25zIHdoZW4gU1RBQyBvYmplY3RzIGFyZSB1cGxvYWRlZCB0byBjb25maWd1cmVkIGJ1Y2tldHNcbiAgICpcbiAgICogVGhlIHRvcGljIGZhbnMgb3V0IHRvIHRoZSBTUVMgcXVldWUgZm9yIGJhdGNoZWQgcHJvY2Vzc2luZy5cbiAgICovXG4gIHB1YmxpYyByZWFkb25seSB0b3BpYzogc25zLlRvcGljO1xuXG4gIC8qKlxuICAgKiBUaGUgU1FTIHF1ZXVlIHRoYXQgYnVmZmVycyBtZXNzYWdlcyBiZWZvcmUgcHJvY2Vzc2luZy5cbiAgICpcbiAgICogVGhpcyBxdWV1ZSBjb2xsZWN0cyBib3RoIGRpcmVjdCBTVEFDIG9iamVjdHMgZnJvbSBTTlMgYW5kIFMzIGV2ZW50XG4gICAqIG5vdGlmaWNhdGlvbnMsIGJhdGNoaW5nIHRoZW0gZm9yIGVmZmljaWVudCBkYXRhYmFzZSBvcGVyYXRpb25zLlxuICAgKiBDb25maWd1cmVkIHdpdGggYSB2aXNpYmlsaXR5IHRpbWVvdXQgdGhhdCBhY2NvbW1vZGF0ZXMgTGFtYmRhXG4gICAqIHByb2Nlc3NpbmcgdGltZSBwbHVzIGJ1ZmZlci5cbiAgICovXG4gIHB1YmxpYyByZWFkb25seSBxdWV1ZTogc3FzLlF1ZXVlO1xuXG4gIC8qKlxuICAgKiBEZWFkIGxldHRlciBxdWV1ZSBmb3IgZmFpbGVkIG9iamVjdHMgbG9hZGluZyBhdHRlbXB0cy5cbiAgICpcbiAgICogTWVzc2FnZXMgdGhhdCBmYWlsIHByb2Nlc3NpbmcgYWZ0ZXIgNSBhdHRlbXB0cyBhcmUgc2VudCBoZXJlXG4gICAqIGZvciBpbnNwZWN0aW9uIGFuZCBwb3RlbnRpYWwgcmVwbGF5LiBSZXRhaW5zIG1lc3NhZ2VzIGZvciAxNCBkYXlzXG4gICAqIHRvIGFsbG93IGZvciBkZWJ1Z2dpbmcgYW5kIG1hbnVhbCBpbnRlcnZlbnRpb24uXG4gICAqXG4gICAqICoqVXNlciBSZXNwb25zaWJpbGl0eSoqOiBUaGlzIGNvbnN0cnVjdCBwcm92aWRlcyBOTyBhdXRvbWF0ZWQgbW9uaXRvcmluZyxcbiAgICogYWxlcnRpbmcsIG9yIHJlcHJvY2Vzc2luZyBvZiBkZWFkIGxldHRlciBxdWV1ZSBtZXNzYWdlcy4gQXBwbGljYXRpb25zXG4gICAqIHVzaW5nIHRoaXMgY29uc3RydWN0IG11c3QgaW1wbGVtZW50IHRoZWlyIG93bjpcbiAgICogLSBEZWFkIGxldHRlciBxdWV1ZSBkZXB0aCBtb25pdG9yaW5nIGFuZCBhbGVydGluZ1xuICAgKiAtIEZhaWxlZCBtZXNzYWdlIGluc3BlY3Rpb24gYW5kIGRlYnVnZ2luZyB3b3JrZmxvd3NcbiAgICogLSBNYW51YWwgb3IgYXV0b21hdGVkIHJlcHJvY2Vzc2luZyBtZWNoYW5pc21zXG4gICAqIC0gQ2xlYW51cCBwcm9jZWR1cmVzIGZvciBvbGQgZmFpbGVkIG1lc3NhZ2VzXG4gICAqL1xuICBwdWJsaWMgcmVhZG9ubHkgZGVhZExldHRlclF1ZXVlOiBzcXMuUXVldWU7XG5cbiAgLyoqXG4gICAqIFRoZSBMYW1iZGEgZnVuY3Rpb24gdGhhdCBsb2FkcyBTVEFDIG9iamVjdHMgaW50byB0aGUgcGdzdGFjIGRhdGFiYXNlLlxuICAgKlxuICAgKiBUaGlzIFB5dGhvbiBmdW5jdGlvbiByZWNlaXZlcyBiYXRjaGVzIG9mIG1lc3NhZ2VzIGZyb20gU1FTIGFuZCBwcm9jZXNzZXNcbiAgICogdGhlbSBiYXNlZCBvbiB0aGVpciB0eXBlOlxuICAgKiAtIERpcmVjdCBTVEFDIG9iamVjdHM6IFZhbGlkYXRlcyBhbmQgbG9hZHMgZGlyZWN0bHkgaW50byBwZ3N0YWNcbiAgICogLSBTMyBldmVudHM6IEZldGNoZXMgU1RBQyBKU09OIGZyb20gUzMsIHZhbGlkYXRlcywgYW5kIGxvYWRzIGludG8gcGdzdGFjXG4gICAqXG4gICAqIFRoZSBmdW5jdGlvbiBjb25uZWN0cyB0byBQb3N0Z3JlU1FMIHVzaW5nIGNyZWRlbnRpYWxzIGZyb20gU2VjcmV0cyBNYW5hZ2VyXG4gICAqIGFuZCB1c2VzIHB5cGdzdGFjIGZvciBlZmZpY2llbnQgZGF0YWJhc2Ugb3BlcmF0aW9ucy5cbiAgICovXG4gIHB1YmxpYyByZWFkb25seSBsYW1iZGFGdW5jdGlvbjogbGFtYmRhLkZ1bmN0aW9uO1xuXG4gIGNvbnN0cnVjdG9yKHNjb3BlOiBDb25zdHJ1Y3QsIGlkOiBzdHJpbmcsIHByb3BzOiBTdGFjTG9hZGVyUHJvcHMpIHtcbiAgICBzdXBlcihzY29wZSwgaWQpO1xuXG4gICAgY29uc3QgdGltZW91dFNlY29uZHMgPSBwcm9wcy5sYW1iZGFUaW1lb3V0U2Vjb25kcyA/PyAzMDA7XG4gICAgY29uc3QgbGFtYmRhUnVudGltZSA9IHByb3BzLmxhbWJkYVJ1bnRpbWUgPz8gbGFtYmRhLlJ1bnRpbWUuUFlUSE9OXzNfMTE7XG4gICAgY29uc3QgbWF4Q29uY3VycmVuY3kgPSBwcm9wcy5tYXhDb25jdXJyZW5jeSA/PyAyO1xuXG4gICAgLy8gQ3JlYXRlIGRlYWQgbGV0dGVyIHF1ZXVlXG4gICAgdGhpcy5kZWFkTGV0dGVyUXVldWUgPSBuZXcgc3FzLlF1ZXVlKHRoaXMsIFwiRGVhZExldHRlclF1ZXVlXCIsIHtcbiAgICAgIHJldGVudGlvblBlcmlvZDogRHVyYXRpb24uZGF5cygxNCksXG4gICAgfSk7XG5cbiAgICAvLyBDcmVhdGUgbWFpbiBxdWV1ZVxuICAgIHRoaXMucXVldWUgPSBuZXcgc3FzLlF1ZXVlKHRoaXMsIFwiUXVldWVcIiwge1xuICAgICAgdmlzaWJpbGl0eVRpbWVvdXQ6IER1cmF0aW9uLnNlY29uZHModGltZW91dFNlY29uZHMgKyAxMCksXG4gICAgICBlbmNyeXB0aW9uOiBzcXMuUXVldWVFbmNyeXB0aW9uLlNRU19NQU5BR0VELFxuICAgICAgZGVhZExldHRlclF1ZXVlOiB7XG4gICAgICAgIG1heFJlY2VpdmVDb3VudDogNSxcbiAgICAgICAgcXVldWU6IHRoaXMuZGVhZExldHRlclF1ZXVlLFxuICAgICAgfSxcbiAgICB9KTtcblxuICAgIC8vIENyZWF0ZSBTTlMgdG9waWNcbiAgICB0aGlzLnRvcGljID0gbmV3IHNucy5Ub3BpYyh0aGlzLCBcIlRvcGljXCIsIHtcbiAgICAgIGRpc3BsYXlOYW1lOiBgJHtpZH0tU3RhY0xvYWRlclRvcGljYCxcbiAgICB9KTtcblxuICAgIC8vIFN1YnNjcmliZSB0aGUgcXVldWUgdG8gdGhlIHRvcGljXG4gICAgdGhpcy50b3BpYy5hZGRTdWJzY3JpcHRpb24oXG4gICAgICBuZXcgc25zU3Vic2NyaXB0aW9ucy5TcXNTdWJzY3JpcHRpb24odGhpcy5xdWV1ZSlcbiAgICApO1xuXG4gICAgLy8gQ3JlYXRlIHRoZSBsYW1iZGEgZnVuY3Rpb25cbiAgICB0aGlzLmxhbWJkYUZ1bmN0aW9uID0gbmV3IGxhbWJkYS5GdW5jdGlvbih0aGlzLCBcIkZ1bmN0aW9uXCIsIHtcbiAgICAgIHJ1bnRpbWU6IGxhbWJkYVJ1bnRpbWUsXG4gICAgICBoYW5kbGVyOiBcInN0YWNfbG9hZGVyLmhhbmRsZXIuaGFuZGxlclwiLFxuICAgICAgdnBjOiBwcm9wcy52cGMsXG4gICAgICB2cGNTdWJuZXRzOiBwcm9wcy5zdWJuZXRTZWxlY3Rpb24sXG4gICAgICBjb2RlOiBsYW1iZGEuQ29kZS5mcm9tRG9ja2VyQnVpbGQocGF0aC5qb2luKF9fZGlybmFtZSwgXCIuLlwiKSwge1xuICAgICAgICBmaWxlOiBcInN0YWMtbG9hZGVyL3J1bnRpbWUvRG9ja2VyZmlsZVwiLFxuICAgICAgICBwbGF0Zm9ybTogXCJsaW51eC9hbWQ2NFwiLFxuICAgICAgICBidWlsZEFyZ3M6IHtcbiAgICAgICAgICBQWVRIT05fVkVSU0lPTjogbGFtYmRhUnVudGltZS50b1N0cmluZygpLnJlcGxhY2UoXCJweXRob25cIiwgXCJcIiksXG4gICAgICAgICAgUEdTVEFDX1ZFUlNJT046IHByb3BzLnBnc3RhY0RiLnBnc3RhY1ZlcnNpb24sXG4gICAgICAgIH0sXG4gICAgICB9KSxcbiAgICAgIG1lbW9yeVNpemU6IHByb3BzLm1lbW9yeVNpemUgPz8gMTAyNCxcbiAgICAgIHRpbWVvdXQ6IER1cmF0aW9uLnNlY29uZHModGltZW91dFNlY29uZHMpLFxuICAgICAgcmVzZXJ2ZWRDb25jdXJyZW50RXhlY3V0aW9uczogbWF4Q29uY3VycmVuY3ksXG4gICAgICBsb2dSZXRlbnRpb246IGxvZ3MuUmV0ZW50aW9uRGF5cy5PTkVfV0VFSyxcbiAgICAgIGVudmlyb25tZW50OiB7XG4gICAgICAgIFBHU1RBQ19TRUNSRVRfQVJOOiBwcm9wcy5wZ3N0YWNEYi5wZ3N0YWNTZWNyZXQuc2VjcmV0QXJuLFxuICAgICAgICAuLi5wcm9wcy5lbnZpcm9ubWVudCxcbiAgICAgIH0sXG4gICAgICAvLyBvdmVyd3JpdGVzIGRlZmF1bHRzIHdpdGggdXNlci1wcm92aWRlZCBjb25maWd1cmFibGUgcHJvcGVydGllc1xuICAgICAgLi4ucHJvcHMubGFtYmRhRnVuY3Rpb25PcHRpb25zLFxuICAgIH0pO1xuXG4gICAgLy8gR3JhbnQgcGVybWlzc2lvbnMgdG8gcmVhZCB0aGUgZGF0YWJhc2Ugc2VjcmV0XG4gICAgcHJvcHMucGdzdGFjRGIucGdzdGFjU2VjcmV0LmdyYW50UmVhZCh0aGlzLmxhbWJkYUZ1bmN0aW9uKTtcblxuICAgIC8vIEFkZCBTUVMgZXZlbnQgc291cmNlIHRvIHRoZSBsYW1iZGFcbiAgICB0aGlzLmxhbWJkYUZ1bmN0aW9uLmFkZEV2ZW50U291cmNlKFxuICAgICAgbmV3IGxhbWJkYUV2ZW50U291cmNlcy5TcXNFdmVudFNvdXJjZSh0aGlzLnF1ZXVlLCB7XG4gICAgICAgIGJhdGNoU2l6ZTogcHJvcHMuYmF0Y2hTaXplID8/IDUwMCxcbiAgICAgICAgbWF4QmF0Y2hpbmdXaW5kb3c6IER1cmF0aW9uLm1pbnV0ZXMoXG4gICAgICAgICAgcHJvcHMubWF4QmF0Y2hpbmdXaW5kb3dNaW51dGVzID8/IDFcbiAgICAgICAgKSxcbiAgICAgICAgbWF4Q29uY3VycmVuY3k6IG1heENvbmN1cnJlbmN5LFxuICAgICAgICByZXBvcnRCYXRjaEl0ZW1GYWlsdXJlczogdHJ1ZSxcbiAgICAgIH0pXG4gICAgKTtcblxuICAgIC8vIENyZWF0ZSBvdXRwdXRzXG4gICAgY29uc3QgZXhwb3J0UHJlZml4ID0gU3RhY2sub2YodGhpcykuc3RhY2tOYW1lO1xuICAgIG5ldyBDZm5PdXRwdXQodGhpcywgXCJUb3BpY0FyblwiLCB7XG4gICAgICB2YWx1ZTogdGhpcy50b3BpYy50b3BpY0FybixcbiAgICAgIGRlc2NyaXB0aW9uOiBcIkFSTiBvZiB0aGUgU3RhY0xvYWRlciBTTlMgVG9waWNcIixcbiAgICAgIGV4cG9ydE5hbWU6IGAke2V4cG9ydFByZWZpeH0tc3RhYy1sb2FkZXItdG9waWMtYXJuYCxcbiAgICB9KTtcblxuICAgIG5ldyBDZm5PdXRwdXQodGhpcywgXCJRdWV1ZVVybFwiLCB7XG4gICAgICB2YWx1ZTogdGhpcy5xdWV1ZS5xdWV1ZVVybCxcbiAgICAgIGRlc2NyaXB0aW9uOiBcIlVSTCBvZiB0aGUgU3RhY0xvYWRlciBTUVMgUXVldWVcIixcbiAgICAgIGV4cG9ydE5hbWU6IGAke2V4cG9ydFByZWZpeH0tc3RhYy1sb2FkZXItcXVldWUtdXJsYCxcbiAgICB9KTtcblxuICAgIG5ldyBDZm5PdXRwdXQodGhpcywgXCJEZWFkTGV0dGVyUXVldWVVcmxcIiwge1xuICAgICAgdmFsdWU6IHRoaXMuZGVhZExldHRlclF1ZXVlLnF1ZXVlVXJsLFxuICAgICAgZGVzY3JpcHRpb246IFwiVVJMIG9mIHRoZSBTdGFjTG9hZGVyIERlYWQgTGV0dGVyIFF1ZXVlXCIsXG4gICAgICBleHBvcnROYW1lOiBgJHtleHBvcnRQcmVmaXh9LXN0YWMtbG9hZGVyLWRlYWRsZXR0ZXItcXVldWUtdXJsYCxcbiAgICB9KTtcblxuICAgIG5ldyBDZm5PdXRwdXQodGhpcywgXCJGdW5jdGlvbk5hbWVcIiwge1xuICAgICAgdmFsdWU6IHRoaXMubGFtYmRhRnVuY3Rpb24uZnVuY3Rpb25OYW1lLFxuICAgICAgZGVzY3JpcHRpb246IFwiTmFtZSBvZiB0aGUgU3RhY0xvYWRlciBMYW1iZGEgRnVuY3Rpb25cIixcbiAgICAgIGV4cG9ydE5hbWU6IGAke2V4cG9ydFByZWZpeH0tc3RhYy1sb2FkZXItZnVuY3Rpb24tbmFtZWAsXG4gICAgfSk7XG4gIH1cbn1cblxuLyoqXG4gKiBAZGVwcmVjYXRlZCBVc2UgU3RhY0xvYWRlciBpbnN0ZWFkLiBTdGFjSXRlbUxvYWRlciB3aWxsIGJlIHJlbW92ZWQgaW4gYSBmdXR1cmUgdmVyc2lvbi5cbiAqL1xuZXhwb3J0IGNsYXNzIFN0YWNJdGVtTG9hZGVyIGV4dGVuZHMgU3RhY0xvYWRlciB7XG4gIGNvbnN0cnVjdG9yKHNjb3BlOiBDb25zdHJ1Y3QsIGlkOiBzdHJpbmcsIHByb3BzOiBTdGFjTG9hZGVyUHJvcHMpIHtcbiAgICBjb25zb2xlLndhcm4oXG4gICAgICBgU3RhY0l0ZW1Mb2FkZXIgaXMgZGVwcmVjYXRlZC4gUGxlYXNlIHVzZSBTdGFjTG9hZGVyIGluc3RlYWQuIGAgK1xuICAgICAgICBgU3RhY0l0ZW1Mb2FkZXIgd2lsbCBiZSByZW1vdmVkIGluIGEgZnV0dXJlIHZlcnNpb24uYFxuICAgICk7XG5cbiAgICBzdXBlcihzY29wZSwgaWQsIHByb3BzKTtcbiAgfVxufVxuXG4vLyBBbHNvIGNyZWF0ZSBhIGRlcHJlY2F0ZWQgaW50ZXJmYWNlIGFsaWFzIGlmIHlvdSBoYWQgYSBzZXBhcmF0ZSBpbnRlcmZhY2Vcbi8qKlxuICogQGRlcHJlY2F0ZWQgVXNlIFN0YWNMb2FkZXJQcm9wcyBpbnN0ZWFkLiBTdGFjSXRlbUxvYWRlclByb3BzIHdpbGwgYmUgcmVtb3ZlZCBpbiBhIGZ1dHVyZSB2ZXJzaW9uLlxuICovXG5leHBvcnQgaW50ZXJmYWNlIFN0YWNJdGVtTG9hZGVyUHJvcHMgZXh0ZW5kcyBTdGFjTG9hZGVyUHJvcHMge31cbiJdfQ==
@@ -7,12 +7,12 @@ ENV PYTHONUNBUFFERED=1
7
7
 
8
8
  WORKDIR /asset
9
9
 
10
- COPY stac-item-loader/runtime/pyproject.toml pyproject.toml
11
- COPY stac-item-loader/runtime/src/stac_item_loader/ stac_item_loader/
10
+ COPY stac-loader/runtime/pyproject.toml pyproject.toml
11
+ COPY stac-loader/runtime/src/stac_loader/ stac_loader/
12
12
 
13
13
  ARG PGSTAC_VERSION=0.9.6
14
14
  RUN uv add --no-sync pypgstac==${PGSTAC_VERSION} && \
15
15
  uv export --no-dev --no-editable -o requirements.txt && \
16
16
  uv pip install --target /asset -r requirements.txt
17
17
 
18
- CMD ["stac_item_loader.handler.handler"]
18
+ CMD ["stac_loader.handler.handler"]
@@ -1,7 +1,7 @@
1
1
  [project]
2
- name = "stac-item-loader"
2
+ name = "stac-loader"
3
3
  version = "0.1.0"
4
- description = "An application for loading STAC items into a pgstac database"
4
+ description = "An application for loading STAC collections and items into a pgstac database"
5
5
  authors = [
6
6
  { name = "hrodmn", email = "henry@developmentseed.org" }
7
7
  ]
@@ -92,40 +92,40 @@ def is_s3_event(message_str: str) -> bool:
92
92
  return "aws:s3" in message_str
93
93
 
94
94
 
95
- def get_stac_item_from_s3(bucket_name: str, object_key: str) -> Dict[str, Any]:
96
- """Fetch STAC item JSON from S3."""
95
+ def get_stac_object_from_s3(bucket_name: str, object_key: str) -> Dict[str, Any]:
96
+ """Fetch STAC JSON from S3."""
97
97
  session = boto3.session.Session()
98
98
  s3_client = session.client("s3")
99
99
 
100
100
  try:
101
- logger.debug(f"Fetching STAC item from s3://{bucket_name}/{object_key}")
101
+ logger.debug(f"Fetching STAC object from s3://{bucket_name}/{object_key}")
102
102
  response = s3_client.get_object(Bucket=bucket_name, Key=object_key)
103
103
  content = response["Body"].read()
104
104
 
105
105
  try:
106
- stac_item_json = content.decode("utf-8")
106
+ stac_json = content.decode("utf-8")
107
107
  except UnicodeDecodeError as e:
108
108
  logger.error(
109
109
  f"Failed to decode S3 object as UTF-8: s3://{bucket_name}/{object_key}"
110
110
  )
111
111
  raise ValueError("S3 object is not valid UTF-8 text") from e
112
112
 
113
- stac_item_data = json.loads(stac_item_json)
113
+ stac_data = json.loads(stac_json)
114
114
  logger.debug(
115
- f"Successfully parsed STAC item from S3: {stac_item_data.get('id', 'unknown')}"
115
+ f"Successfully parsed STAC metadata from S3: {stac_data.get('id', 'unknown')}"
116
116
  )
117
117
 
118
- return stac_item_data
118
+ return stac_data
119
119
 
120
120
  except Exception as e:
121
121
  logger.error(
122
- f"Failed to fetch STAC item from s3://{bucket_name}/{object_key}: {e}"
122
+ f"Failed to fetch STAC metadata from s3://{bucket_name}/{object_key}: {e}"
123
123
  )
124
124
  raise
125
125
 
126
126
 
127
127
  def process_s3_event(message_str: str) -> Dict[str, Any]:
128
- """Process an S3 event notification and return STAC item data."""
128
+ """Process an S3 event notification and return STAC metadata."""
129
129
  try:
130
130
  message_data = json.loads(message_str)
131
131
  records: List[Dict[str, Any]] = message_data.get("Records", [])
@@ -138,15 +138,15 @@ def process_s3_event(message_str: str) -> Dict[str, Any]:
138
138
  bucket_name = s3_data["bucket"]["name"]
139
139
  object_key = s3_data["object"]["key"]
140
140
 
141
- # Validate that this looks like a STAC item file
141
+ # Validate that this looks like a STAC file
142
142
  if not object_key.endswith((".json", ".geojson")):
143
143
  raise ValueError(
144
- f"S3 object key does not appear to be a STAC item: {object_key}"
144
+ f"S3 object key does not appear to be a STAC document: {object_key}"
145
145
  )
146
146
 
147
- stac_item_data = get_stac_item_from_s3(bucket_name, object_key)
147
+ stac_data = get_stac_object_from_s3(bucket_name, object_key)
148
148
 
149
- return stac_item_data
149
+ return stac_data
150
150
 
151
151
  except KeyError as e:
152
152
  logger.error(f"S3 event missing required field: {e}")
@@ -169,8 +169,10 @@ def handler(
169
169
  )
170
170
  pgstac_dsn = get_pgstac_dsn()
171
171
 
172
- batch_item_failures: List[BatchItemFailure] = []
172
+ batch_failures: List[BatchItemFailure] = []
173
173
 
174
+ collections: List[Dict[str, Any]] = []
175
+ collection_message_ids: List[str] = []
174
176
  items_by_collection: DefaultDict[str, List[Dict[str, Any]]] = defaultdict(list)
175
177
  message_ids_by_collection: DefaultDict[str, List[str]] = defaultdict(list)
176
178
 
@@ -194,21 +196,47 @@ def handler(
194
196
  else:
195
197
  message_data = json.loads(message_str)
196
198
 
197
- item = Item(**message_data)
199
+ if message_data["type"] == "Feature":
200
+ item = Item(**message_data)
198
201
 
199
- if not item.collection:
200
- raise KeyError(f"item {item.id} is missing a collection id!")
202
+ if not item.collection:
203
+ raise KeyError(f"item {item.id} is missing a collection id!")
204
+
205
+ items_by_collection[item.collection].append(item.model_dump(mode="json"))
206
+ message_ids_by_collection[item.collection].append(message_id)
207
+ elif message_data["type"] == "Collection":
208
+ collection = Collection(**message_data)
209
+ collections.append(collection.model_dump(mode="json"))
210
+ collection_message_ids.append(message_id)
211
+ else:
212
+ raise ValueError(
213
+ f"expected either a 'Feature' or a 'Collection', received a {message_data['type']}"
214
+ )
201
215
 
202
- items_by_collection[item.collection].append(item.model_dump(mode="json"))
203
- message_ids_by_collection[item.collection].append(message_id)
204
216
  logger.debug(f"[{message_id}] Successfully processed.")
205
217
 
206
218
  except (ValueError, KeyError, ValidationError, json.JSONDecodeError) as e:
207
219
  logger.error(f"[{message_id}] Failed with error: {e}", extra=record)
208
- batch_item_failures.append({"itemIdentifier": message_id})
220
+ batch_failures.append({"itemIdentifier": message_id})
209
221
  except Exception as e:
210
222
  logger.error(f"[{message_id}] Unexpected error: {e}", extra=record)
211
- batch_item_failures.append({"itemIdentifier": message_id})
223
+ batch_failures.append({"itemIdentifier": message_id})
224
+
225
+ if collections:
226
+ try:
227
+ with PgstacDB(dsn=pgstac_dsn) as db:
228
+ loader = Loader(db=db)
229
+ logger.info("loading collections into database.")
230
+ loader.load_collections(
231
+ file=collections, # type: ignore
232
+ insert_mode=Methods.upsert,
233
+ )
234
+ logger.info(f"successfully loaded {len(collections)} collections.")
235
+ except Exception as e:
236
+ logger.error(f"failed to load collections: {str(e)}")
237
+ batch_failures.extend(
238
+ [{"itemIdentifier": message_id} for message_id in collection_message_ids]
239
+ )
212
240
 
213
241
  for collection_id, items in items_by_collection.items():
214
242
  try:
@@ -248,21 +276,21 @@ def handler(
248
276
  except Exception as e:
249
277
  logger.error(f"[{collection_id}] failed to load items: {str(e)}")
250
278
 
251
- batch_item_failures.extend(
279
+ batch_failures.extend(
252
280
  [
253
281
  {"itemIdentifier": message_id}
254
282
  for message_id in message_ids_by_collection[collection_id]
255
283
  ]
256
284
  )
257
285
 
258
- if batch_item_failures:
286
+ if batch_failures:
259
287
  logger.warning(
260
- f"Finished processing batch. {len(batch_item_failures)} failure(s) reported."
288
+ f"Finished processing batch. {len(batch_failures)} failure(s) reported."
261
289
  )
262
290
  logger.info(
263
- f"Returning failed item identifiers: {[f['itemIdentifier'] for f in batch_item_failures]}"
291
+ f"Returning failed item identifiers: {[f['itemIdentifier'] for f in batch_failures]}"
264
292
  )
265
- return {"batchItemFailures": batch_item_failures}
293
+ return {"batchItemFailures": batch_failures}
266
294
  else:
267
295
  logger.info("Finished processing batch. All records successful.")
268
296
  return None
@@ -93,7 +93,7 @@ export interface StactoolsItemGeneratorProps {
93
93
  /**
94
94
  * ARN of the SNS topic to publish generated items to.
95
95
  *
96
- * This is typically the topic from a StacItemLoader construct.
96
+ * This is typically the topic from a StacLoader construct.
97
97
  * Generated STAC items will be published here for downstream
98
98
  * processing and database insertion.
99
99
  */
@@ -158,7 +158,7 @@ export interface StactoolsItemGeneratorProps {
158
158
  *
159
159
  * ```typescript
160
160
  * // Create item loader first (or get existing topic ARN)
161
- * const loader = new StacItemLoader(this, 'ItemLoader', {
161
+ * const loader = new StacLoader(this, 'ItemLoader', {
162
162
  * pgstacDb: database
163
163
  * });
164
164
  *