eoapi-cdk 5.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 (67) hide show
  1. package/.devcontainer/devcontainer.json +4 -0
  2. package/.github/workflows/build.yaml +73 -0
  3. package/.github/workflows/conventional-pr.yaml +26 -0
  4. package/.github/workflows/distribute.yaml +45 -0
  5. package/.github/workflows/docs.yaml +26 -0
  6. package/.github/workflows/test.yaml +13 -0
  7. package/.github/workflows/tox.yaml +24 -0
  8. package/.jsii +5058 -0
  9. package/.nvmrc +1 -0
  10. package/CHANGELOG.md +195 -0
  11. package/README.md +50 -0
  12. package/diagrams/bastion_diagram.excalidraw +1416 -0
  13. package/diagrams/bastion_diagram.png +0 -0
  14. package/diagrams/ingestor_diagram.excalidraw +2274 -0
  15. package/diagrams/ingestor_diagram.png +0 -0
  16. package/lib/bastion-host/index.d.ts +117 -0
  17. package/lib/bastion-host/index.js +162 -0
  18. package/lib/bootstrapper/index.d.ts +57 -0
  19. package/lib/bootstrapper/index.js +73 -0
  20. package/lib/bootstrapper/runtime/Dockerfile +18 -0
  21. package/lib/bootstrapper/runtime/handler.py +235 -0
  22. package/lib/database/index.d.ts +60 -0
  23. package/lib/database/index.js +84 -0
  24. package/lib/database/instance-memory.json +525 -0
  25. package/lib/index.d.ts +6 -0
  26. package/lib/index.js +19 -0
  27. package/lib/ingestor-api/index.d.ts +54 -0
  28. package/lib/ingestor-api/index.js +147 -0
  29. package/lib/ingestor-api/runtime/dev_requirements.txt +2 -0
  30. package/lib/ingestor-api/runtime/requirements.txt +12 -0
  31. package/lib/ingestor-api/runtime/src/__init__.py +0 -0
  32. package/lib/ingestor-api/runtime/src/collection.py +36 -0
  33. package/lib/ingestor-api/runtime/src/config.py +46 -0
  34. package/lib/ingestor-api/runtime/src/dependencies.py +94 -0
  35. package/lib/ingestor-api/runtime/src/handler.py +9 -0
  36. package/lib/ingestor-api/runtime/src/ingestor.py +82 -0
  37. package/lib/ingestor-api/runtime/src/loader.py +21 -0
  38. package/lib/ingestor-api/runtime/src/main.py +125 -0
  39. package/lib/ingestor-api/runtime/src/schemas.py +148 -0
  40. package/lib/ingestor-api/runtime/src/services.py +44 -0
  41. package/lib/ingestor-api/runtime/src/utils.py +52 -0
  42. package/lib/ingestor-api/runtime/src/validators.py +72 -0
  43. package/lib/ingestor-api/runtime/tests/__init__.py +0 -0
  44. package/lib/ingestor-api/runtime/tests/conftest.py +271 -0
  45. package/lib/ingestor-api/runtime/tests/test_collection.py +35 -0
  46. package/lib/ingestor-api/runtime/tests/test_collection_endpoint.py +41 -0
  47. package/lib/ingestor-api/runtime/tests/test_ingestor.py +60 -0
  48. package/lib/ingestor-api/runtime/tests/test_registration.py +198 -0
  49. package/lib/ingestor-api/runtime/tests/test_utils.py +35 -0
  50. package/lib/stac-api/index.d.ts +50 -0
  51. package/lib/stac-api/index.js +60 -0
  52. package/lib/stac-api/runtime/requirements.txt +8 -0
  53. package/lib/stac-api/runtime/src/__init__.py +0 -0
  54. package/lib/stac-api/runtime/src/app.py +58 -0
  55. package/lib/stac-api/runtime/src/config.py +96 -0
  56. package/lib/stac-api/runtime/src/handler.py +9 -0
  57. package/lib/titiler-pgstac-api/index.d.ts +33 -0
  58. package/lib/titiler-pgstac-api/index.js +67 -0
  59. package/lib/titiler-pgstac-api/runtime/Dockerfile +20 -0
  60. package/lib/titiler-pgstac-api/runtime/dev_requirements.txt +1 -0
  61. package/lib/titiler-pgstac-api/runtime/requirements.txt +3 -0
  62. package/lib/titiler-pgstac-api/runtime/src/__init__.py +3 -0
  63. package/lib/titiler-pgstac-api/runtime/src/handler.py +23 -0
  64. package/lib/titiler-pgstac-api/runtime/src/utils.py +26 -0
  65. package/package.json +81 -0
  66. package/tox.ini +52 -0
  67. package/tsconfig.tsbuildinfo +18116 -0
@@ -0,0 +1,235 @@
1
+ """
2
+ Custom resource lambda handler to bootstrap Postgres db.
3
+ Source: https://github.com/developmentseed/eoAPI/blob/master/deployment/handlers/db_handler.py
4
+ """
5
+ import json
6
+
7
+ import boto3
8
+ import psycopg
9
+ import httpx
10
+ from psycopg import sql
11
+ from psycopg.conninfo import make_conninfo
12
+ from pypgstac.db import PgstacDB
13
+ from pypgstac.migrate import Migrate
14
+
15
+
16
+ def send(
17
+ event,
18
+ context,
19
+ responseStatus,
20
+ responseData,
21
+ physicalResourceId=None,
22
+ noEcho=False,
23
+ ):
24
+ """
25
+ Copyright 2016 Amazon Web Services, Inc. or its affiliates. All Rights Reserved.
26
+ This file is licensed to you under the AWS Customer Agreement (the "License").
27
+ You may not use this file except in compliance with the License.
28
+ A copy of the License is located at http://aws.amazon.com/agreement/ .
29
+ This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or implied.
30
+ See the License for the specific language governing permissions and limitations under the License.
31
+
32
+ Send response from AWS Lambda.
33
+
34
+ Note: The cfnresponse module is available only when you use the ZipFile property to write your source code.
35
+ It isn't available for source code that's stored in Amazon S3 buckets.
36
+ For code in buckets, you must write your own functions to send responses.
37
+ """
38
+ responseUrl = event["ResponseURL"]
39
+
40
+ print(responseUrl)
41
+
42
+ responseBody = {}
43
+ responseBody["Status"] = responseStatus
44
+ responseBody["Reason"] = (
45
+ "See the details in CloudWatch Log Stream: " + context.log_stream_name
46
+ )
47
+ responseBody["PhysicalResourceId"] = physicalResourceId or context.log_stream_name
48
+ responseBody["StackId"] = event["StackId"]
49
+ responseBody["RequestId"] = event["RequestId"]
50
+ responseBody["LogicalResourceId"] = event["LogicalResourceId"]
51
+ responseBody["NoEcho"] = noEcho
52
+ responseBody["Data"] = responseData
53
+
54
+ json_responseBody = json.dumps(responseBody)
55
+
56
+ print("Response body:\n" + json_responseBody)
57
+
58
+ headers = {"content-type": "", "content-length": str(len(json_responseBody))}
59
+
60
+ try:
61
+ response = httpx.put(responseUrl, data=json_responseBody, headers=headers)
62
+ print("Status code: " + response.reason)
63
+ except Exception as e:
64
+ print("send(..) failed executing httpx.put(..): " + str(e))
65
+
66
+
67
+ def get_secret(secret_name):
68
+ """Get Secrets from secret manager."""
69
+ print(f"Fetching {secret_name}")
70
+ client = boto3.client(service_name="secretsmanager")
71
+ response = client.get_secret_value(SecretId=secret_name)
72
+ return json.loads(response["SecretString"])
73
+
74
+
75
+ def create_db(cursor, db_name: str) -> None:
76
+ """Create DB."""
77
+ cursor.execute(
78
+ sql.SQL("SELECT 1 FROM pg_catalog.pg_database " "WHERE datname = %s"), [db_name]
79
+ )
80
+ if cursor.fetchone():
81
+ print(f"database {db_name} exists, not creating DB")
82
+ else:
83
+ print(f"database {db_name} not found, creating...")
84
+ cursor.execute(
85
+ sql.SQL("CREATE DATABASE {db_name}").format(db_name=sql.Identifier(db_name))
86
+ )
87
+
88
+
89
+ def create_user(cursor, username: str, password: str) -> None:
90
+ """Create User."""
91
+ cursor.execute(
92
+ sql.SQL(
93
+ "DO $$ "
94
+ "BEGIN "
95
+ " IF NOT EXISTS ( "
96
+ " SELECT 1 FROM pg_roles "
97
+ " WHERE rolname = {user}) "
98
+ " THEN "
99
+ " CREATE USER {username} "
100
+ " WITH PASSWORD {password}; "
101
+ " ELSE "
102
+ " ALTER USER {username} "
103
+ " WITH PASSWORD {password}; "
104
+ " END IF; "
105
+ "END "
106
+ "$$; "
107
+ ).format(username=sql.Identifier(username), password=password, user=username)
108
+ )
109
+
110
+
111
+ def create_permissions(cursor, db_name: str, username: str) -> None:
112
+ """Add permissions."""
113
+ cursor.execute(
114
+ sql.SQL(
115
+ "GRANT CONNECT ON DATABASE {db_name} TO {username};"
116
+ "GRANT CREATE ON DATABASE {db_name} TO {username};" # Allow schema creation
117
+ "GRANT USAGE ON SCHEMA public TO {username};"
118
+ "ALTER DEFAULT PRIVILEGES IN SCHEMA public "
119
+ "GRANT ALL PRIVILEGES ON TABLES TO {username};"
120
+ "ALTER DEFAULT PRIVILEGES IN SCHEMA public "
121
+ "GRANT ALL PRIVILEGES ON SEQUENCES TO {username};"
122
+ "GRANT pgstac_read TO {username};"
123
+ "GRANT pgstac_ingest TO {username};"
124
+ "GRANT pgstac_admin TO {username};"
125
+ ).format(
126
+ db_name=sql.Identifier(db_name),
127
+ username=sql.Identifier(username),
128
+ )
129
+ )
130
+
131
+
132
+ def register_extensions(cursor) -> None:
133
+ """Add PostGIS extension."""
134
+ cursor.execute(sql.SQL("CREATE EXTENSION IF NOT EXISTS postgis;"))
135
+
136
+
137
+ def handler(event, context):
138
+ """Lambda Handler."""
139
+ print(f"Handling {event}")
140
+
141
+ if event["RequestType"] not in ["Create", "Update"]:
142
+ return send(event, context, "SUCCESS", {"msg": "No action to be taken"})
143
+
144
+ try:
145
+ params = event["ResourceProperties"]
146
+ connection_params = get_secret(params["conn_secret_arn"])
147
+ user_params = get_secret(params["new_user_secret_arn"])
148
+
149
+ print("Connecting to admin DB...")
150
+ admin_db_conninfo = make_conninfo(
151
+ dbname=connection_params.get("dbname", "postgres"),
152
+ user=connection_params["username"],
153
+ password=connection_params["password"],
154
+ host=connection_params["host"],
155
+ port=connection_params["port"],
156
+ )
157
+ with psycopg.connect(admin_db_conninfo, autocommit=True) as conn:
158
+ with conn.cursor() as cur:
159
+ print("Creating database...")
160
+ create_db(
161
+ cursor=cur,
162
+ db_name=user_params["dbname"],
163
+ )
164
+
165
+ print("Creating user...")
166
+ create_user(
167
+ cursor=cur,
168
+ username=user_params["username"],
169
+ password=user_params["password"],
170
+ )
171
+
172
+ # Install extensions on the user DB with
173
+ # superuser permissions, since they will
174
+ # otherwise fail to install when run as
175
+ # the non-superuser within the pgstac
176
+ # migrations.
177
+ print("Connecting to STAC DB...")
178
+ stac_db_conninfo = make_conninfo(
179
+ dbname=user_params["dbname"],
180
+ user=connection_params["username"],
181
+ password=connection_params["password"],
182
+ host=connection_params["host"],
183
+ port=connection_params["port"],
184
+ )
185
+ with psycopg.connect(stac_db_conninfo, autocommit=True) as conn:
186
+ with conn.cursor() as cur:
187
+ print("Registering PostGIS ...")
188
+ register_extensions(cursor=cur)
189
+
190
+ stac_db_admin_dsn = (
191
+ "postgresql://{user}:{password}@{host}:{port}/{dbname}".format(
192
+ dbname=user_params.get("dbname", "postgres"),
193
+ user=connection_params["username"],
194
+ password=connection_params["password"],
195
+ host=connection_params["host"],
196
+ port=connection_params["port"],
197
+ )
198
+ )
199
+
200
+ pgdb = PgstacDB(dsn=stac_db_admin_dsn, debug=True)
201
+ print(f"Current {pgdb.version=}")
202
+
203
+ # As admin, run migrations
204
+ print("Running migrations...")
205
+ Migrate(pgdb).run_migration(params["pgstac_version"])
206
+
207
+ # Assign appropriate permissions to user (requires pgSTAC migrations to have run)
208
+ with psycopg.connect(admin_db_conninfo, autocommit=True) as conn:
209
+ with conn.cursor() as cur:
210
+ print("Setting permissions...")
211
+ create_permissions(
212
+ cursor=cur,
213
+ db_name=user_params["dbname"],
214
+ username=user_params["username"],
215
+ )
216
+
217
+ print("Adding mosaic index...")
218
+ with psycopg.connect(
219
+ stac_db_admin_dsn,
220
+ autocommit=True,
221
+ options="-c search_path=pgstac,public -c application_name=pgstac",
222
+ ) as conn:
223
+ conn.execute(
224
+ sql.SQL(
225
+ "CREATE INDEX IF NOT EXISTS searches_mosaic ON searches ((true)) WHERE metadata->>'type'='mosaic';"
226
+ )
227
+ )
228
+
229
+ except Exception as e:
230
+ print(f"Unable to bootstrap database with exception={e}")
231
+ send(event, context, "FAILED", {"message": str(e)})
232
+ raise e
233
+
234
+ print("Complete.")
235
+ return send(event, context, "SUCCESS", {})
@@ -0,0 +1,60 @@
1
+ import { aws_rds as rds, aws_secretsmanager as secretsmanager } from "aws-cdk-lib";
2
+ import { Construct } from "constructs";
3
+ import { BootstrapPgStacProps } from "../bootstrapper";
4
+ /**
5
+ * An RDS instance with pgSTAC installed. This is a wrapper around the
6
+ * `rds.DatabaseInstance` higher-level construct making use
7
+ * of the BootstrapPgStac construct.
8
+ */
9
+ export declare class PgStacDatabase extends Construct {
10
+ db: rds.DatabaseInstance;
11
+ pgstacSecret: secretsmanager.ISecret;
12
+ constructor(scope: Construct, id: string, props: PgStacDatabaseProps);
13
+ getParameters(instanceType: string, parameters: PgStacDatabaseProps["parameters"]): DatabaseParameters;
14
+ }
15
+ export interface PgStacDatabaseProps extends rds.DatabaseInstanceProps {
16
+ readonly pgstacDbName?: BootstrapPgStacProps["pgstacDbName"];
17
+ readonly pgstacVersion?: BootstrapPgStacProps["pgstacVersion"];
18
+ readonly pgstacUsername?: BootstrapPgStacProps["pgstacUsername"];
19
+ readonly secretsPrefix?: BootstrapPgStacProps["secretsPrefix"];
20
+ }
21
+ export interface DatabaseParameters {
22
+ /**
23
+ * @default - LEAST({DBInstanceClassMemory/9531392}, 5000)
24
+ */
25
+ readonly maxConnections: string;
26
+ /**
27
+ * Note: This value is measured in 8KB blocks.
28
+ *
29
+ * @default '{DBInstanceClassMemory/32768}' 25% of instance memory, ie `{(DBInstanceClassMemory/(1024*8)) * 0.25}`
30
+ */
31
+ readonly sharedBuffers: string;
32
+ /**
33
+ * @default - 75% of instance memory
34
+ */
35
+ readonly effectiveCacheSize: string;
36
+ /**
37
+ * @default - shared buffers divided by max connections
38
+ */
39
+ readonly workMem: string;
40
+ /**
41
+ * @default - 25% of shared buffers
42
+ */
43
+ readonly maintenanceWorkMem: string;
44
+ /**
45
+ * @default 1024
46
+ */
47
+ readonly maxLocksPerTransaction: string;
48
+ /**
49
+ * @default 131172 (128 * 1024)
50
+ */
51
+ readonly tempBuffers: string;
52
+ /**
53
+ * @default 1
54
+ */
55
+ readonly seqPageCost: string;
56
+ /**
57
+ * @default 1.1
58
+ */
59
+ readonly randomPageCost: string;
60
+ }
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ var _a;
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.PgStacDatabase = 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 bootstrapper_1 = require("../bootstrapper");
9
+ const instanceSizes = require("./instance-memory.json");
10
+ /**
11
+ * An RDS instance with pgSTAC installed. This is a wrapper around the
12
+ * `rds.DatabaseInstance` higher-level construct making use
13
+ * of the BootstrapPgStac construct.
14
+ */
15
+ class PgStacDatabase extends constructs_1.Construct {
16
+ constructor(scope, id, props) {
17
+ super(scope, id);
18
+ const defaultParameters = this.getParameters(props.instanceType?.toString() || "m5.large", props.parameters);
19
+ const parameterGroup = new aws_cdk_lib_1.aws_rds.ParameterGroup(this, "parameterGroup", {
20
+ engine: props.engine,
21
+ parameters: {
22
+ shared_buffers: defaultParameters.sharedBuffers,
23
+ effective_cache_size: defaultParameters.effectiveCacheSize,
24
+ work_mem: defaultParameters.workMem,
25
+ maintenance_work_mem: defaultParameters.maintenanceWorkMem,
26
+ max_locks_per_transaction: defaultParameters.maxLocksPerTransaction,
27
+ temp_buffers: defaultParameters.tempBuffers,
28
+ seq_page_cost: defaultParameters.seqPageCost,
29
+ random_page_cost: defaultParameters.randomPageCost,
30
+ ...props.parameters,
31
+ },
32
+ });
33
+ this.db = new aws_cdk_lib_1.aws_rds.DatabaseInstance(this, "db", {
34
+ instanceIdentifier: aws_cdk_lib_1.Stack.of(this).stackName,
35
+ parameterGroup,
36
+ ...props,
37
+ });
38
+ const bootstrap = new bootstrapper_1.BootstrapPgStac(this, "bootstrap-pgstac-instance", {
39
+ vpc: props.vpc,
40
+ database: this.db,
41
+ dbSecret: this.db.secret,
42
+ pgstacDbName: props.pgstacDbName,
43
+ pgstacVersion: props.pgstacVersion,
44
+ pgstacUsername: props.pgstacUsername,
45
+ secretsPrefix: props.secretsPrefix,
46
+ });
47
+ this.pgstacSecret = bootstrap.secret;
48
+ }
49
+ getParameters(instanceType, parameters) {
50
+ // https://github.com/aws/aws-cli/issues/1279#issuecomment-909318236
51
+ const memory_in_kb = instanceSizes[instanceType] * 1024;
52
+ // It's only necessary to consider passed in parameters for any value that used to
53
+ // derive subsequent values. Values that don't have dependencies will be overriden
54
+ // when we unpack the passed-in user parameters
55
+ const maxConnections = parameters?.maxConnections
56
+ ? Number.parseInt(parameters.maxConnections)
57
+ : // https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Limits.html#RDS_Limits.MaxConnections
58
+ Math.min(Math.round((memory_in_kb * 1024) / 9531392), 5000);
59
+ const sharedBuffers = parameters?.sharedBufers
60
+ ? Number.parseInt(parameters.sharedBufers)
61
+ : Math.round(0.25 * memory_in_kb);
62
+ const effectiveCacheSize = Math.round(0.75 * memory_in_kb);
63
+ const workMem = Math.floor(sharedBuffers / maxConnections);
64
+ const maintenanceWorkMem = Math.round(0.25 * sharedBuffers);
65
+ const tempBuffers = 128 * 1024;
66
+ const seqPageCost = 1;
67
+ const randomPageCost = 1.1;
68
+ return {
69
+ maxConnections: `${maxConnections}`,
70
+ sharedBuffers: `${sharedBuffers / 8}`,
71
+ effectiveCacheSize: `${effectiveCacheSize}`,
72
+ workMem: `${workMem}`,
73
+ maintenanceWorkMem: `${maintenanceWorkMem}`,
74
+ maxLocksPerTransaction: "1024",
75
+ tempBuffers: `${tempBuffers}`,
76
+ seqPageCost: `${seqPageCost}`,
77
+ randomPageCost: `${randomPageCost}`,
78
+ };
79
+ }
80
+ }
81
+ exports.PgStacDatabase = PgStacDatabase;
82
+ _a = JSII_RTTI_SYMBOL_1;
83
+ PgStacDatabase[_a] = { fqn: "eoapi-cdk.PgStacDatabase", version: "5.0.0" };
84
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJpbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7OztBQUFBLDZDQUlxQjtBQUNyQiwyQ0FBdUM7QUFDdkMsa0RBQXdFO0FBRXhFLE1BQU0sYUFBYSxHQUEyQixPQUFPLENBQUMsd0JBQXdCLENBQUMsQ0FBQztBQUVoRjs7OztHQUlHO0FBQ0gsTUFBYSxjQUFlLFNBQVEsc0JBQVM7SUFJM0MsWUFBWSxLQUFnQixFQUFFLEVBQVUsRUFBRSxLQUEwQjtRQUNsRSxLQUFLLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxDQUFDO1FBRWpCLE1BQU0saUJBQWlCLEdBQUcsSUFBSSxDQUFDLGFBQWEsQ0FDMUMsS0FBSyxDQUFDLFlBQVksRUFBRSxRQUFRLEVBQUUsSUFBSSxVQUFVLEVBQzVDLEtBQUssQ0FBQyxVQUFVLENBQ2pCLENBQUM7UUFDRixNQUFNLGNBQWMsR0FBRyxJQUFJLHFCQUFHLENBQUMsY0FBYyxDQUFDLElBQUksRUFBRSxnQkFBZ0IsRUFBRTtZQUNwRSxNQUFNLEVBQUUsS0FBSyxDQUFDLE1BQU07WUFDcEIsVUFBVSxFQUFFO2dCQUNWLGNBQWMsRUFBRSxpQkFBaUIsQ0FBQyxhQUFhO2dCQUMvQyxvQkFBb0IsRUFBRSxpQkFBaUIsQ0FBQyxrQkFBa0I7Z0JBQzFELFFBQVEsRUFBRSxpQkFBaUIsQ0FBQyxPQUFPO2dCQUNuQyxvQkFBb0IsRUFBRSxpQkFBaUIsQ0FBQyxrQkFBa0I7Z0JBQzFELHlCQUF5QixFQUFFLGlCQUFpQixDQUFDLHNCQUFzQjtnQkFDbkUsWUFBWSxFQUFFLGlCQUFpQixDQUFDLFdBQVc7Z0JBQzNDLGFBQWEsRUFBRSxpQkFBaUIsQ0FBQyxXQUFXO2dCQUM1QyxnQkFBZ0IsRUFBRSxpQkFBaUIsQ0FBQyxjQUFjO2dCQUNsRCxHQUFHLEtBQUssQ0FBQyxVQUFVO2FBQ3BCO1NBQ0YsQ0FBQyxDQUFDO1FBRUgsSUFBSSxDQUFDLEVBQUUsR0FBRyxJQUFJLHFCQUFHLENBQUMsZ0JBQWdCLENBQUMsSUFBSSxFQUFFLElBQUksRUFBRTtZQUM3QyxrQkFBa0IsRUFBRSxtQkFBSyxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsQ0FBQyxTQUFTO1lBQzVDLGNBQWM7WUFDZCxHQUFHLEtBQUs7U0FDVCxDQUFDLENBQUM7UUFFSCxNQUFNLFNBQVMsR0FBRyxJQUFJLDhCQUFlLENBQUMsSUFBSSxFQUFFLDJCQUEyQixFQUFFO1lBQ3ZFLEdBQUcsRUFBRSxLQUFLLENBQUMsR0FBRztZQUNkLFFBQVEsRUFBRSxJQUFJLENBQUMsRUFBRTtZQUNqQixRQUFRLEVBQUUsSUFBSSxDQUFDLEVBQUUsQ0FBQyxNQUFPO1lBQ3pCLFlBQVksRUFBRSxLQUFLLENBQUMsWUFBWTtZQUNoQyxhQUFhLEVBQUUsS0FBSyxDQUFDLGFBQWE7WUFDbEMsY0FBYyxFQUFFLEtBQUssQ0FBQyxjQUFjO1lBQ3BDLGFBQWEsRUFBRSxLQUFLLENBQUMsYUFBYTtTQUNuQyxDQUFDLENBQUM7UUFFSCxJQUFJLENBQUMsWUFBWSxHQUFHLFNBQVMsQ0FBQyxNQUFNLENBQUM7SUFDdkMsQ0FBQztJQUVNLGFBQWEsQ0FDbEIsWUFBb0IsRUFDcEIsVUFBNkM7UUFFN0Msb0VBQW9FO1FBQ3BFLE1BQU0sWUFBWSxHQUFHLGFBQWEsQ0FBQyxZQUFZLENBQUMsR0FBRyxJQUFJLENBQUM7UUFFeEQsa0ZBQWtGO1FBQ2xGLGtGQUFrRjtRQUNsRiwrQ0FBK0M7UUFDL0MsTUFBTSxjQUFjLEdBQUcsVUFBVSxFQUFFLGNBQWM7WUFDL0MsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQUMsVUFBVSxDQUFDLGNBQWMsQ0FBQztZQUM1QyxDQUFDLENBQUMsb0dBQW9HO2dCQUNwRyxJQUFJLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsQ0FBQyxZQUFZLEdBQUcsSUFBSSxDQUFDLEdBQUcsT0FBTyxDQUFDLEVBQUUsSUFBSSxDQUFDLENBQUM7UUFDaEUsTUFBTSxhQUFhLEdBQUcsVUFBVSxFQUFFLFlBQVk7WUFDNUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQUMsVUFBVSxDQUFDLFlBQVksQ0FBQztZQUMxQyxDQUFDLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLEdBQUcsWUFBWSxDQUFDLENBQUM7UUFFcEMsTUFBTSxrQkFBa0IsR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksR0FBRyxZQUFZLENBQUMsQ0FBQztRQUMzRCxNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLGFBQWEsR0FBRyxjQUFjLENBQUMsQ0FBQztRQUMzRCxNQUFNLGtCQUFrQixHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxHQUFHLGFBQWEsQ0FBQyxDQUFDO1FBRTVELE1BQU0sV0FBVyxHQUFHLEdBQUcsR0FBRyxJQUFJLENBQUM7UUFDL0IsTUFBTSxXQUFXLEdBQUcsQ0FBQyxDQUFDO1FBQ3RCLE1BQU0sY0FBYyxHQUFHLEdBQUcsQ0FBQztRQUUzQixPQUFPO1lBQ0wsY0FBYyxFQUFFLEdBQUcsY0FBYyxFQUFFO1lBQ25DLGFBQWEsRUFBRSxHQUFHLGFBQWEsR0FBRyxDQUFDLEVBQUU7WUFDckMsa0JBQWtCLEVBQUUsR0FBRyxrQkFBa0IsRUFBRTtZQUMzQyxPQUFPLEVBQUUsR0FBRyxPQUFPLEVBQUU7WUFDckIsa0JBQWtCLEVBQUUsR0FBRyxrQkFBa0IsRUFBRTtZQUMzQyxzQkFBc0IsRUFBRSxNQUFNO1lBQzlCLFdBQVcsRUFBRSxHQUFHLFdBQVcsRUFBRTtZQUM3QixXQUFXLEVBQUUsR0FBRyxXQUFXLEVBQUU7WUFDN0IsY0FBYyxFQUFFLEdBQUcsY0FBYyxFQUFFO1NBQ3BDLENBQUM7SUFDSixDQUFDOztBQWxGSCx3Q0FtRkMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQge1xuICBTdGFjayxcbiAgYXdzX3JkcyBhcyByZHMsXG4gIGF3c19zZWNyZXRzbWFuYWdlciBhcyBzZWNyZXRzbWFuYWdlcixcbn0gZnJvbSBcImF3cy1jZGstbGliXCI7XG5pbXBvcnQgeyBDb25zdHJ1Y3QgfSBmcm9tIFwiY29uc3RydWN0c1wiO1xuaW1wb3J0IHsgQm9vdHN0cmFwUGdTdGFjLCBCb290c3RyYXBQZ1N0YWNQcm9wcyB9IGZyb20gXCIuLi9ib290c3RyYXBwZXJcIjtcblxuY29uc3QgaW5zdGFuY2VTaXplczogUmVjb3JkPHN0cmluZywgbnVtYmVyPiA9IHJlcXVpcmUoXCIuL2luc3RhbmNlLW1lbW9yeS5qc29uXCIpO1xuXG4vKipcbiAqIEFuIFJEUyBpbnN0YW5jZSB3aXRoIHBnU1RBQyBpbnN0YWxsZWQuIFRoaXMgaXMgYSB3cmFwcGVyIGFyb3VuZCB0aGVcbiAqIGByZHMuRGF0YWJhc2VJbnN0YW5jZWAgaGlnaGVyLWxldmVsIGNvbnN0cnVjdCBtYWtpbmcgdXNlXG4gKiBvZiB0aGUgQm9vdHN0cmFwUGdTdGFjIGNvbnN0cnVjdC5cbiAqL1xuZXhwb3J0IGNsYXNzIFBnU3RhY0RhdGFiYXNlIGV4dGVuZHMgQ29uc3RydWN0IHtcbiAgZGI6IHJkcy5EYXRhYmFzZUluc3RhbmNlO1xuICBwZ3N0YWNTZWNyZXQ6IHNlY3JldHNtYW5hZ2VyLklTZWNyZXQ7XG5cbiAgY29uc3RydWN0b3Ioc2NvcGU6IENvbnN0cnVjdCwgaWQ6IHN0cmluZywgcHJvcHM6IFBnU3RhY0RhdGFiYXNlUHJvcHMpIHtcbiAgICBzdXBlcihzY29wZSwgaWQpO1xuXG4gICAgY29uc3QgZGVmYXVsdFBhcmFtZXRlcnMgPSB0aGlzLmdldFBhcmFtZXRlcnMoXG4gICAgICBwcm9wcy5pbnN0YW5jZVR5cGU/LnRvU3RyaW5nKCkgfHwgXCJtNS5sYXJnZVwiLFxuICAgICAgcHJvcHMucGFyYW1ldGVyc1xuICAgICk7XG4gICAgY29uc3QgcGFyYW1ldGVyR3JvdXAgPSBuZXcgcmRzLlBhcmFtZXRlckdyb3VwKHRoaXMsIFwicGFyYW1ldGVyR3JvdXBcIiwge1xuICAgICAgZW5naW5lOiBwcm9wcy5lbmdpbmUsXG4gICAgICBwYXJhbWV0ZXJzOiB7XG4gICAgICAgIHNoYXJlZF9idWZmZXJzOiBkZWZhdWx0UGFyYW1ldGVycy5zaGFyZWRCdWZmZXJzLFxuICAgICAgICBlZmZlY3RpdmVfY2FjaGVfc2l6ZTogZGVmYXVsdFBhcmFtZXRlcnMuZWZmZWN0aXZlQ2FjaGVTaXplLFxuICAgICAgICB3b3JrX21lbTogZGVmYXVsdFBhcmFtZXRlcnMud29ya01lbSxcbiAgICAgICAgbWFpbnRlbmFuY2Vfd29ya19tZW06IGRlZmF1bHRQYXJhbWV0ZXJzLm1haW50ZW5hbmNlV29ya01lbSxcbiAgICAgICAgbWF4X2xvY2tzX3Blcl90cmFuc2FjdGlvbjogZGVmYXVsdFBhcmFtZXRlcnMubWF4TG9ja3NQZXJUcmFuc2FjdGlvbixcbiAgICAgICAgdGVtcF9idWZmZXJzOiBkZWZhdWx0UGFyYW1ldGVycy50ZW1wQnVmZmVycyxcbiAgICAgICAgc2VxX3BhZ2VfY29zdDogZGVmYXVsdFBhcmFtZXRlcnMuc2VxUGFnZUNvc3QsXG4gICAgICAgIHJhbmRvbV9wYWdlX2Nvc3Q6IGRlZmF1bHRQYXJhbWV0ZXJzLnJhbmRvbVBhZ2VDb3N0LFxuICAgICAgICAuLi5wcm9wcy5wYXJhbWV0ZXJzLFxuICAgICAgfSxcbiAgICB9KTtcblxuICAgIHRoaXMuZGIgPSBuZXcgcmRzLkRhdGFiYXNlSW5zdGFuY2UodGhpcywgXCJkYlwiLCB7XG4gICAgICBpbnN0YW5jZUlkZW50aWZpZXI6IFN0YWNrLm9mKHRoaXMpLnN0YWNrTmFtZSxcbiAgICAgIHBhcmFtZXRlckdyb3VwLFxuICAgICAgLi4ucHJvcHMsXG4gICAgfSk7XG5cbiAgICBjb25zdCBib290c3RyYXAgPSBuZXcgQm9vdHN0cmFwUGdTdGFjKHRoaXMsIFwiYm9vdHN0cmFwLXBnc3RhYy1pbnN0YW5jZVwiLCB7XG4gICAgICB2cGM6IHByb3BzLnZwYyxcbiAgICAgIGRhdGFiYXNlOiB0aGlzLmRiLFxuICAgICAgZGJTZWNyZXQ6IHRoaXMuZGIuc2VjcmV0ISxcbiAgICAgIHBnc3RhY0RiTmFtZTogcHJvcHMucGdzdGFjRGJOYW1lLFxuICAgICAgcGdzdGFjVmVyc2lvbjogcHJvcHMucGdzdGFjVmVyc2lvbixcbiAgICAgIHBnc3RhY1VzZXJuYW1lOiBwcm9wcy5wZ3N0YWNVc2VybmFtZSxcbiAgICAgIHNlY3JldHNQcmVmaXg6IHByb3BzLnNlY3JldHNQcmVmaXgsXG4gICAgfSk7XG5cbiAgICB0aGlzLnBnc3RhY1NlY3JldCA9IGJvb3RzdHJhcC5zZWNyZXQ7XG4gIH1cblxuICBwdWJsaWMgZ2V0UGFyYW1ldGVycyhcbiAgICBpbnN0YW5jZVR5cGU6IHN0cmluZyxcbiAgICBwYXJhbWV0ZXJzOiBQZ1N0YWNEYXRhYmFzZVByb3BzW1wicGFyYW1ldGVyc1wiXVxuICApOiBEYXRhYmFzZVBhcmFtZXRlcnMge1xuICAgIC8vIGh0dHBzOi8vZ2l0aHViLmNvbS9hd3MvYXdzLWNsaS9pc3N1ZXMvMTI3OSNpc3N1ZWNvbW1lbnQtOTA5MzE4MjM2XG4gICAgY29uc3QgbWVtb3J5X2luX2tiID0gaW5zdGFuY2VTaXplc1tpbnN0YW5jZVR5cGVdICogMTAyNDtcblxuICAgIC8vIEl0J3Mgb25seSBuZWNlc3NhcnkgdG8gY29uc2lkZXIgcGFzc2VkIGluIHBhcmFtZXRlcnMgZm9yIGFueSB2YWx1ZSB0aGF0IHVzZWQgdG9cbiAgICAvLyBkZXJpdmUgc3Vic2VxdWVudCB2YWx1ZXMuIFZhbHVlcyB0aGF0IGRvbid0IGhhdmUgZGVwZW5kZW5jaWVzIHdpbGwgYmUgb3ZlcnJpZGVuXG4gICAgLy8gd2hlbiB3ZSB1bnBhY2sgdGhlIHBhc3NlZC1pbiB1c2VyIHBhcmFtZXRlcnNcbiAgICBjb25zdCBtYXhDb25uZWN0aW9ucyA9IHBhcmFtZXRlcnM/Lm1heENvbm5lY3Rpb25zXG4gICAgICA/IE51bWJlci5wYXJzZUludChwYXJhbWV0ZXJzLm1heENvbm5lY3Rpb25zKVxuICAgICAgOiAvLyBodHRwczovL2RvY3MuYXdzLmFtYXpvbi5jb20vQW1hem9uUkRTL2xhdGVzdC9Vc2VyR3VpZGUvQ0hBUF9MaW1pdHMuaHRtbCNSRFNfTGltaXRzLk1heENvbm5lY3Rpb25zXG4gICAgICAgIE1hdGgubWluKE1hdGgucm91bmQoKG1lbW9yeV9pbl9rYiAqIDEwMjQpIC8gOTUzMTM5MiksIDUwMDApO1xuICAgIGNvbnN0IHNoYXJlZEJ1ZmZlcnMgPSBwYXJhbWV0ZXJzPy5zaGFyZWRCdWZlcnNcbiAgICAgID8gTnVtYmVyLnBhcnNlSW50KHBhcmFtZXRlcnMuc2hhcmVkQnVmZXJzKVxuICAgICAgOiBNYXRoLnJvdW5kKDAuMjUgKiBtZW1vcnlfaW5fa2IpO1xuXG4gICAgY29uc3QgZWZmZWN0aXZlQ2FjaGVTaXplID0gTWF0aC5yb3VuZCgwLjc1ICogbWVtb3J5X2luX2tiKTtcbiAgICBjb25zdCB3b3JrTWVtID0gTWF0aC5mbG9vcihzaGFyZWRCdWZmZXJzIC8gbWF4Q29ubmVjdGlvbnMpO1xuICAgIGNvbnN0IG1haW50ZW5hbmNlV29ya01lbSA9IE1hdGgucm91bmQoMC4yNSAqIHNoYXJlZEJ1ZmZlcnMpO1xuXG4gICAgY29uc3QgdGVtcEJ1ZmZlcnMgPSAxMjggKiAxMDI0O1xuICAgIGNvbnN0IHNlcVBhZ2VDb3N0ID0gMTtcbiAgICBjb25zdCByYW5kb21QYWdlQ29zdCA9IDEuMTtcblxuICAgIHJldHVybiB7XG4gICAgICBtYXhDb25uZWN0aW9uczogYCR7bWF4Q29ubmVjdGlvbnN9YCxcbiAgICAgIHNoYXJlZEJ1ZmZlcnM6IGAke3NoYXJlZEJ1ZmZlcnMgLyA4fWAsIC8vIFJlcHJlc2VudGVkIGluIDhrYiBibG9ja3NcbiAgICAgIGVmZmVjdGl2ZUNhY2hlU2l6ZTogYCR7ZWZmZWN0aXZlQ2FjaGVTaXplfWAsXG4gICAgICB3b3JrTWVtOiBgJHt3b3JrTWVtfWAsXG4gICAgICBtYWludGVuYW5jZVdvcmtNZW06IGAke21haW50ZW5hbmNlV29ya01lbX1gLFxuICAgICAgbWF4TG9ja3NQZXJUcmFuc2FjdGlvbjogXCIxMDI0XCIsXG4gICAgICB0ZW1wQnVmZmVyczogYCR7dGVtcEJ1ZmZlcnN9YCxcbiAgICAgIHNlcVBhZ2VDb3N0OiBgJHtzZXFQYWdlQ29zdH1gLFxuICAgICAgcmFuZG9tUGFnZUNvc3Q6IGAke3JhbmRvbVBhZ2VDb3N0fWAsXG4gICAgfTtcbiAgfVxufVxuXG5leHBvcnQgaW50ZXJmYWNlIFBnU3RhY0RhdGFiYXNlUHJvcHMgZXh0ZW5kcyByZHMuRGF0YWJhc2VJbnN0YW5jZVByb3BzIHtcbiAgcmVhZG9ubHkgcGdzdGFjRGJOYW1lPzogQm9vdHN0cmFwUGdTdGFjUHJvcHNbXCJwZ3N0YWNEYk5hbWVcIl07XG4gIHJlYWRvbmx5IHBnc3RhY1ZlcnNpb24/OiBCb290c3RyYXBQZ1N0YWNQcm9wc1tcInBnc3RhY1ZlcnNpb25cIl07XG4gIHJlYWRvbmx5IHBnc3RhY1VzZXJuYW1lPzogQm9vdHN0cmFwUGdTdGFjUHJvcHNbXCJwZ3N0YWNVc2VybmFtZVwiXTtcbiAgcmVhZG9ubHkgc2VjcmV0c1ByZWZpeD86IEJvb3RzdHJhcFBnU3RhY1Byb3BzW1wic2VjcmV0c1ByZWZpeFwiXTtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBEYXRhYmFzZVBhcmFtZXRlcnMge1xuICAvKipcbiAgICogQGRlZmF1bHQgLSBMRUFTVCh7REJJbnN0YW5jZUNsYXNzTWVtb3J5Lzk1MzEzOTJ9LCA1MDAwKVxuICAgKi9cbiAgcmVhZG9ubHkgbWF4Q29ubmVjdGlvbnM6IHN0cmluZztcblxuICAvKipcbiAgICogTm90ZTogVGhpcyB2YWx1ZSBpcyBtZWFzdXJlZCBpbiA4S0IgYmxvY2tzLlxuICAgKlxuICAgKiBAZGVmYXVsdCAne0RCSW5zdGFuY2VDbGFzc01lbW9yeS8zMjc2OH0nIDI1JSBvZiBpbnN0YW5jZSBtZW1vcnksIGllIGB7KERCSW5zdGFuY2VDbGFzc01lbW9yeS8oMTAyNCo4KSkgKiAwLjI1fWBcbiAgICovXG4gIHJlYWRvbmx5IHNoYXJlZEJ1ZmZlcnM6IHN0cmluZztcblxuICAvKipcbiAgICogQGRlZmF1bHQgLSA3NSUgb2YgaW5zdGFuY2UgbWVtb3J5XG4gICAqL1xuICByZWFkb25seSBlZmZlY3RpdmVDYWNoZVNpemU6IHN0cmluZztcblxuICAvKipcbiAgICogQGRlZmF1bHQgLSBzaGFyZWQgYnVmZmVycyBkaXZpZGVkIGJ5IG1heCBjb25uZWN0aW9uc1xuICAgKi9cbiAgcmVhZG9ubHkgd29ya01lbTogc3RyaW5nO1xuXG4gIC8qKlxuICAgKiBAZGVmYXVsdCAtIDI1JSBvZiBzaGFyZWQgYnVmZmVyc1xuICAgKi9cbiAgcmVhZG9ubHkgbWFpbnRlbmFuY2VXb3JrTWVtOiBzdHJpbmc7XG5cbiAgLyoqXG4gICAqIEBkZWZhdWx0IDEwMjRcbiAgICovXG4gIHJlYWRvbmx5IG1heExvY2tzUGVyVHJhbnNhY3Rpb246IHN0cmluZztcblxuICAvKipcbiAgICogQGRlZmF1bHQgMTMxMTcyICgxMjggKiAxMDI0KVxuICAgKi9cbiAgcmVhZG9ubHkgdGVtcEJ1ZmZlcnM6IHN0cmluZztcblxuICAvKipcbiAgICogQGRlZmF1bHQgMVxuICAgKi9cbiAgcmVhZG9ubHkgc2VxUGFnZUNvc3Q6IHN0cmluZztcblxuICAvKipcbiAgICogQGRlZmF1bHQgMS4xXG4gICAqL1xuICByZWFkb25seSByYW5kb21QYWdlQ29zdDogc3RyaW5nO1xufVxuIl19