tinybird 0.0.1.dev34__py3-none-any.whl → 0.0.1.dev36__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of tinybird might be problematic. Click here for more details.

@@ -1,803 +0,0 @@
1
- # This is a command file for our CLI. Please keep it clean.
2
- #
3
- # - If it makes sense and only when strictly necessary, you can create utility functions in this file.
4
- # - But please, **do not** interleave utility functions and command definitions.
5
-
6
- import os
7
- from os import getcwd
8
- from pathlib import Path
9
- from typing import Any, Dict, List, Optional
10
-
11
- import aiofiles
12
- import click
13
- from click import Context
14
-
15
- from tinybird.client import DoesNotExistException, TinyB
16
- from tinybird.tb.modules.cli import cli
17
- from tinybird.tb.modules.common import (
18
- ConnectionReplacements,
19
- DataConnectorType,
20
- _get_setting_value,
21
- coro,
22
- create_aws_iamrole_connection,
23
- echo_safe_humanfriendly_tables_format_smart_table,
24
- get_ca_pem_content,
25
- validate_aws_iamrole_connection_name,
26
- validate_aws_iamrole_integration,
27
- validate_connection_name,
28
- validate_kafka_auto_offset_reset,
29
- validate_kafka_bootstrap_servers,
30
- validate_kafka_key,
31
- validate_kafka_schema_registry_url,
32
- validate_kafka_secret,
33
- validate_string_connector_param,
34
- )
35
- from tinybird.tb.modules.exceptions import CLIConnectionException
36
- from tinybird.tb.modules.feedback_manager import FeedbackManager
37
- from tinybird.tb.modules.telemetry import is_ci_environment
38
-
39
- DATA_CONNECTOR_SETTINGS: Dict[DataConnectorType, List[str]] = {
40
- DataConnectorType.KAFKA: [
41
- "kafka_bootstrap_servers",
42
- "kafka_sasl_plain_username",
43
- "kafka_sasl_plain_password",
44
- "cli_version",
45
- "endpoint",
46
- "kafka_security_protocol",
47
- "kafka_sasl_mechanism",
48
- "kafka_schema_registry_url",
49
- "kafka_ssl_ca_pem",
50
- ],
51
- DataConnectorType.GCLOUD_SCHEDULER: ["gcscheduler_region"],
52
- DataConnectorType.SNOWFLAKE: [
53
- "account",
54
- "username",
55
- "password",
56
- "role",
57
- "warehouse",
58
- "warehouse_size",
59
- "stage",
60
- "integration",
61
- ],
62
- DataConnectorType.BIGQUERY: ["account"],
63
- DataConnectorType.GCLOUD_STORAGE: [
64
- "gcs_private_key_id",
65
- "gcs_client_x509_cert_url",
66
- "gcs_project_id",
67
- "gcs_client_id",
68
- "gcs_client_email",
69
- "gcs_private_key",
70
- ],
71
- DataConnectorType.GCLOUD_STORAGE_HMAC: [
72
- "gcs_hmac_access_id",
73
- "gcs_hmac_secret",
74
- ],
75
- DataConnectorType.GCLOUD_STORAGE_SA: ["account_email"],
76
- DataConnectorType.AMAZON_S3: [
77
- "s3_access_key_id",
78
- "s3_secret_access_key",
79
- "s3_region",
80
- ],
81
- DataConnectorType.AMAZON_S3_IAMROLE: [
82
- "s3_iamrole_arn",
83
- "s3_iamrole_region",
84
- "s3_iamrole_external_id",
85
- ],
86
- DataConnectorType.AMAZON_DYNAMODB: [
87
- "dynamodb_iamrole_arn",
88
- "dynamodb_iamrole_region",
89
- "dynamodb_iamrole_external_id",
90
- ],
91
- }
92
-
93
- SENSITIVE_CONNECTOR_SETTINGS = {
94
- DataConnectorType.KAFKA: ["kafka_sasl_plain_password"],
95
- DataConnectorType.GCLOUD_SCHEDULER: [
96
- "gcscheduler_target_url",
97
- "gcscheduler_job_name",
98
- "gcscheduler_region",
99
- ],
100
- DataConnectorType.GCLOUD_STORAGE_HMAC: ["gcs_hmac_secret"],
101
- DataConnectorType.AMAZON_S3: ["s3_secret_access_key"],
102
- DataConnectorType.AMAZON_S3_IAMROLE: ["s3_iamrole_arn"],
103
- DataConnectorType.AMAZON_DYNAMODB: ["dynamodb_iamrole_arn"],
104
- }
105
-
106
-
107
- @cli.group()
108
- @click.pass_context
109
- def connection(ctx: Context) -> None:
110
- """Connection commands."""
111
-
112
-
113
- @connection.group(name="create")
114
- @click.pass_context
115
- def connection_create(ctx: Context) -> None:
116
- """Connection Create commands."""
117
-
118
-
119
- @connection_create.command(name="kafka", short_help="Add a Kafka connection")
120
- @click.option("--bootstrap-servers", help="Kafka Bootstrap Server in form mykafka.mycloud.com:9092")
121
- @click.option("--key", help="Key")
122
- @click.option("--secret", help="Secret")
123
- @click.option(
124
- "--connection-name",
125
- default=None,
126
- help="The name of your Kafka connection. If not provided, it's set as the bootstrap server",
127
- )
128
- @click.option(
129
- "--auto-offset-reset", default=None, help="Offset reset, can be 'latest' or 'earliest'. Defaults to 'latest'."
130
- )
131
- @click.option("--schema-registry-url", default=None, help="Avro Confluent Schema Registry URL")
132
- @click.option(
133
- "--sasl-mechanism",
134
- default="PLAIN",
135
- help="Authentication method for connection-based protocols. Defaults to 'PLAIN'",
136
- )
137
- @click.option("--ssl-ca-pem", default=None, help="Path or content of the CA Certificate file in PEM format")
138
- @click.pass_context
139
- @coro
140
- async def connection_create_kafka(
141
- ctx: Context,
142
- bootstrap_servers: str,
143
- key: str,
144
- secret: str,
145
- connection_name: Optional[str],
146
- auto_offset_reset: Optional[str],
147
- schema_registry_url: Optional[str],
148
- sasl_mechanism: Optional[str],
149
- ssl_ca_pem: Optional[str],
150
- ) -> None:
151
- """
152
- Add a Kafka connection
153
-
154
- \b
155
- $ tb connection create kafka --bootstrap-servers google.com:80 --key a --secret b --connection-name c
156
- """
157
-
158
- bootstrap_servers and validate_kafka_bootstrap_servers(bootstrap_servers)
159
- key and validate_kafka_key(key)
160
- secret and validate_kafka_secret(secret)
161
- schema_registry_url and validate_kafka_schema_registry_url(schema_registry_url)
162
- auto_offset_reset and validate_kafka_auto_offset_reset(auto_offset_reset)
163
-
164
- if not bootstrap_servers:
165
- bootstrap_servers = click.prompt("Kafka Bootstrap Server")
166
- validate_kafka_bootstrap_servers(bootstrap_servers)
167
- if not key:
168
- key = click.prompt("Key")
169
- validate_kafka_key(key)
170
- if not secret:
171
- secret = click.prompt("Secret", hide_input=True)
172
- validate_kafka_secret(secret)
173
- if not connection_name:
174
- connection_name = click.prompt(
175
- f"Connection name (optional, current: {bootstrap_servers})", default=bootstrap_servers
176
- )
177
-
178
- obj: Dict[str, Any] = ctx.ensure_object(dict)
179
- client: TinyB = obj["client"]
180
-
181
- result = await client.connection_create_kafka(
182
- bootstrap_servers,
183
- key,
184
- secret,
185
- connection_name,
186
- auto_offset_reset,
187
- schema_registry_url,
188
- sasl_mechanism,
189
- get_ca_pem_content(ssl_ca_pem),
190
- )
191
-
192
- id = result["id"]
193
- click.echo(FeedbackManager.success_connection_created(id=id))
194
-
195
-
196
- @connection_create.command(name="snowflake", short_help="Creates a Snowflake connection in the current workspace")
197
- @click.option("--account", help="The account identifier of your Snowflake account (e.g. myorg-account123)")
198
- @click.option("--username", help="The Snowflake user you want to use for the connection")
199
- @click.option("--password", help="The Snowflake password of the chosen user")
200
- @click.option(
201
- "--warehouse",
202
- default=None,
203
- help="If not provided, it's set to your Snowflake user default. Warehouse to run the export sentences.",
204
- )
205
- @click.option(
206
- "--role",
207
- default=None,
208
- help="If not provided, it's set to your Snowflake user default. Snowflake role use in the export process.",
209
- )
210
- @click.option(
211
- "--connection-name",
212
- default=None,
213
- help="The name of your Snowflake connection. If not provided, it's set as the account identifier",
214
- )
215
- @click.option(
216
- "--integration-name",
217
- default=None,
218
- help="The name of your Snowflake integration. If not provided, we will create one.",
219
- )
220
- @click.option(
221
- "--stage-name", default=None, help="The name of your Snowflake stage. If not provided, we will create one."
222
- )
223
- @click.option("--no-validate", is_flag=True, help="Do not validate Snowflake permissions during connection creation")
224
- @click.pass_context
225
- @coro
226
- async def connection_create_snowflake(
227
- ctx: Context,
228
- account: Optional[str],
229
- username: Optional[str],
230
- password: Optional[str],
231
- warehouse: Optional[str],
232
- role: Optional[str],
233
- connection_name: Optional[str],
234
- integration_name: Optional[str],
235
- stage_name: Optional[str],
236
- no_validate: Optional[bool],
237
- ) -> None:
238
- """
239
- Creates a Snowflake connection in the current workspace
240
-
241
- \b
242
- $ tb connection create snowflake
243
- """
244
-
245
- obj: Dict[str, Any] = ctx.ensure_object(dict)
246
- client: TinyB = obj["client"]
247
-
248
- is_connection_valid: bool = True
249
-
250
- if not username:
251
- username = click.prompt("User (must have created stage and create integration in Snowflake)")
252
- assert isinstance(username, str)
253
-
254
- if not password:
255
- password = click.prompt("Password", hide_input=True)
256
- assert isinstance(password, str)
257
-
258
- if not account:
259
- account = click.prompt("Account identifier")
260
- assert isinstance(account, str)
261
-
262
- account_parts = account.split(".", maxsplit=1)
263
- if len(account_parts) == 2:
264
- account = "-".join(account_parts)
265
-
266
- if not role:
267
- roles = await client.get_snowflake_roles(account, username, password) or []
268
- default_role = roles[0] if len(roles) else ""
269
- role = click.prompt(
270
- "Role (optional)",
271
- type=click.types.Choice(roles, case_sensitive=False),
272
- show_choices=True,
273
- default=default_role,
274
- show_default=True,
275
- )
276
- assert isinstance(role, str)
277
-
278
- if not warehouse:
279
- warehouses = await client.get_snowflake_warehouses(account, username, password, role) or []
280
- warehouses_names = [w["name"] for w in warehouses]
281
- default_warehouse = warehouses_names[0] if len(warehouses_names) else ""
282
- warehouse = click.prompt(
283
- "Warehouse (optional)",
284
- type=click.types.Choice(warehouses_names, case_sensitive=False),
285
- default=default_warehouse,
286
- show_default=False,
287
- )
288
- assert isinstance(warehouse, str)
289
-
290
- if connection_name and no_validate is False:
291
- if await client.get_connector(connection_name, "snowflake") is not None:
292
- raise CLIConnectionException(FeedbackManager.info_connection_already_exists(name=connection_name))
293
- else:
294
- while not connection_name:
295
- connection_name = click.prompt(
296
- f"Connection name (optional, current: {account})", default=account, show_default=False
297
- )
298
- assert isinstance(connection_name, str)
299
-
300
- if no_validate is False and await client.get_connector(connection_name, "snowflake") is not None:
301
- click.echo(FeedbackManager.info_connection_already_exists(name=connection_name))
302
- connection_name = None
303
- assert isinstance(connection_name, str)
304
-
305
- show_instructions: bool = not is_ci_environment()
306
-
307
- if show_instructions:
308
- instructions = await client.get_snowflake_integration_query(role, stage_name, integration_name)
309
- if instructions:
310
- for step in instructions.get("steps", []):
311
- click.echo(step.get("description"))
312
- click.echo("\n------")
313
- click.echo(step.get("action"))
314
- click.echo("------\n")
315
-
316
- while True:
317
- ans: str = click.prompt(
318
- "Ready?", type=click.types.Choice(["Y", "n"], case_sensitive=False), default="Y", show_default=True
319
- )
320
- if ans.lower() == "y":
321
- break
322
-
323
- conn_file_name = f"{connection_name}.connection"
324
- conn_file_path = Path(getcwd(), conn_file_name)
325
- if os.path.isfile(conn_file_path):
326
- raise CLIConnectionException(FeedbackManager.error_connection_file_already_exists(name=conn_file_name))
327
-
328
- if no_validate is False:
329
- click.echo("** Validating connection...")
330
- is_connection_valid = await client.validate_snowflake_connection(account, username, password)
331
-
332
- if not is_connection_valid:
333
- raise CLIConnectionException(FeedbackManager.error_snowflake_improper_permissions())
334
-
335
- _ = await client.connection_create_snowflake(
336
- account, username, password, warehouse, role, connection_name, integration_name, stage_name
337
- )
338
-
339
- async with aiofiles.open(conn_file_path, "w") as f:
340
- await f.write(
341
- f"""TYPE snowflake
342
-
343
- USERNAME='{username}'
344
- ACCOUNT='{account}'
345
- WAREHOUSE='{warehouse}'
346
- ROLE='{role}'
347
- """
348
- )
349
- click.echo(FeedbackManager.success_connection_file_created(name=conn_file_name))
350
-
351
-
352
- @connection_create.command(name="bigquery", short_help="Add a BigQuery connection")
353
- @click.option("--no-validate", is_flag=True, help="Do not validate GCP permissions during connection creation")
354
- @click.pass_context
355
- @coro
356
- async def connection_create_bigquery(ctx: Context, no_validate: bool) -> None:
357
- """
358
- Add a BigQuery connection
359
-
360
- \b
361
- $ tb connection create bigquery
362
- """
363
-
364
- obj: Dict[str, Any] = ctx.ensure_object(dict)
365
- client: TinyB = obj["client"]
366
-
367
- gcp_account_details: Dict[str, Any] = await client.get_gcp_service_account_details()
368
-
369
- connection_created: bool = False
370
-
371
- while True:
372
- response = click.prompt(
373
- FeedbackManager.prompt_bigquery_account(service_account=gcp_account_details["account"]),
374
- type=click.Choice(["y", "N"], case_sensitive=False),
375
- default="N",
376
- show_default=True,
377
- show_choices=True,
378
- )
379
-
380
- if response in ("n", "N"):
381
- click.echo(FeedbackManager.info_cancelled_by_user())
382
- break
383
-
384
- if no_validate or await client.check_gcp_read_permissions():
385
- connection_created = True
386
- break
387
- else:
388
- click.echo("\n")
389
- click.echo(FeedbackManager.error_bigquery_improper_permissions())
390
-
391
- if connection_created:
392
- async with aiofiles.open(Path(getcwd(), "bigquery.connection"), "w") as f:
393
- await f.write("TYPE bigquery\n")
394
- click.echo(FeedbackManager.success_connection_created(id="bigquery"))
395
-
396
-
397
- @connection.command(name="rm")
398
- @click.argument("connection_id_or_name")
399
- @click.option(
400
- "--force", default=False, help="Force connection removal even if there are datasources currently using it"
401
- )
402
- @click.pass_context
403
- @coro
404
- async def connection_rm(ctx: Context, connection_id_or_name: str, force: bool) -> None:
405
- """Remove a connection."""
406
-
407
- obj: Dict[str, Any] = ctx.ensure_object(dict)
408
- client: TinyB = obj["client"]
409
-
410
- try:
411
- await client.connector_delete(connection_id_or_name)
412
- except DoesNotExistException:
413
- connections = await client.connections()
414
- connection = next(
415
- (connection for connection in connections if connection["name"] == connection_id_or_name), None
416
- )
417
- if connection:
418
- try:
419
- await client.connector_delete(connection["id"])
420
- except DoesNotExistException:
421
- raise CLIConnectionException(
422
- FeedbackManager.error_connection_does_not_exists(connection_id=connection_id_or_name)
423
- )
424
- else:
425
- raise CLIConnectionException(
426
- FeedbackManager.error_connection_does_not_exists(connection_id=connection_id_or_name)
427
- )
428
- except Exception as e:
429
- raise CLIConnectionException(FeedbackManager.error_exception(error=e))
430
- click.echo(FeedbackManager.success_delete_connection(connection_id=connection_id_or_name))
431
-
432
-
433
- @connection.command(name="ls")
434
- @click.option("--connector", help="Filter by connector")
435
- @click.pass_context
436
- @coro
437
- async def connection_ls(ctx: Context, connector: Optional[DataConnectorType] = None) -> None:
438
- """List connections."""
439
- obj: Dict[str, Any] = ctx.ensure_object(dict)
440
- client: TinyB = obj["client"]
441
-
442
- connections = await client.connections(connector=connector)
443
- columns = []
444
- table = []
445
-
446
- click.echo(FeedbackManager.info_connections())
447
-
448
- if not connector:
449
- sensitive_settings = []
450
- columns = ["service", "name", "id", "connected_datasources"]
451
- else:
452
- sensitive_settings = SENSITIVE_CONNECTOR_SETTINGS.get(connector, [])
453
- columns = ["service", "name", "id", "connected_datasources"]
454
- if connector_settings := DATA_CONNECTOR_SETTINGS.get(connector):
455
- columns += connector_settings
456
-
457
- for connection in connections:
458
- row = [_get_setting_value(connection, setting, sensitive_settings) for setting in columns]
459
- table.append(row)
460
-
461
- column_names = [c.replace("kafka_", "") for c in columns]
462
- echo_safe_humanfriendly_tables_format_smart_table(table, column_names=column_names)
463
- click.echo("\n")
464
-
465
-
466
- @connection_create.command(name="s3", short_help="Creates a AWS S3 connection in the current workspace")
467
- @click.option("--key", help="Your Amazon S3 key with access to the buckets")
468
- @click.option("--secret", help="The Amazon S3 secret for the key")
469
- @click.option("--region", help=" The Amazon S3 region where you buckets are located")
470
- @click.option("--connection-name", default=None, help="The name of the connection to identify it in Tinybird")
471
- @click.option("--no-validate", is_flag=True, help="Do not validate S3 permissions during connection creation")
472
- @click.pass_context
473
- @coro
474
- async def connection_create_s3(
475
- ctx: Context,
476
- key: Optional[str],
477
- secret: Optional[str],
478
- region: Optional[str],
479
- connection_name: Optional[str],
480
- no_validate: Optional[bool],
481
- ) -> None:
482
- """
483
- Creates a S3 connection in the current workspace
484
-
485
- \b
486
- $ tb connection create s3
487
- """
488
-
489
- obj: Dict[str, Any] = ctx.ensure_object(dict)
490
- client: TinyB = obj["client"]
491
-
492
- is_connection_valid = True
493
- service = "s3"
494
-
495
- if not key:
496
- key = click.prompt("Key")
497
- validate_string_connector_param("Key", key)
498
-
499
- if not secret:
500
- secret = click.prompt("Secret", hide_input=True)
501
- validate_string_connector_param("Secret", secret)
502
-
503
- if not region:
504
- region = click.prompt("Region")
505
- validate_string_connector_param("Region", region)
506
-
507
- if not connection_name:
508
- connection_name = click.prompt(f"Connection name (optional, current: {key})", default=key)
509
- await validate_connection_name(client, connection_name, service)
510
-
511
- conn_file_name = f"{connection_name}.connection"
512
- conn_file_path = Path(getcwd(), conn_file_name)
513
-
514
- if os.path.isfile(conn_file_path):
515
- raise CLIConnectionException(FeedbackManager.error_connection_file_already_exists(name=conn_file_name))
516
-
517
- params = ConnectionReplacements.map_api_params_from_prompt_params(
518
- service, key=key, secret=secret, region=region, connection_name=connection_name
519
- )
520
-
521
- if not no_validate:
522
- click.echo("** Validating connection...")
523
- is_connection_valid = await client.validate_preview_connection(service, params)
524
-
525
- if not is_connection_valid:
526
- raise CLIConnectionException(FeedbackManager.error_connection_improper_permissions())
527
-
528
- click.echo("** Creating connection...")
529
- _ = await client.connection_create(params)
530
-
531
- async with aiofiles.open(conn_file_path, "w") as f:
532
- await f.write(
533
- """TYPE s3
534
-
535
- """
536
- )
537
- click.echo(FeedbackManager.success_connection_file_created(name=conn_file_name))
538
-
539
-
540
- @connection_create.command(
541
- name="gcs_hmac", short_help="Creates a GCS HMAC connection in the current workspace", hidden=True
542
- )
543
- @click.option("--key", help="Your GCS key with access to the buckets")
544
- @click.option("--secret", help="The GCS secret for the key")
545
- @click.option("--region", help=" The GCS region where you buckets are located")
546
- @click.option("--connection-name", default=None, help="The name of the connection to identify it in Tinybird")
547
- @click.pass_context
548
- @coro
549
- async def connection_create_gcs_hmac(
550
- ctx: Context,
551
- key: Optional[str],
552
- secret: Optional[str],
553
- region: Optional[str],
554
- connection_name: Optional[str],
555
- ) -> None:
556
- """
557
- Creates a GCS HMAC connection in the current workspace
558
-
559
- \b
560
- $ tb connection create gcs_hmac
561
- """
562
-
563
- obj: Dict[str, Any] = ctx.ensure_object(dict)
564
- client: TinyB = obj["client"]
565
- service = "gcs_hmac"
566
-
567
- if not key:
568
- key = click.prompt("Key")
569
- validate_string_connector_param("Key", key)
570
-
571
- if not secret:
572
- secret = click.prompt("Secret", hide_input=True)
573
- validate_string_connector_param("Secret", secret)
574
-
575
- if not region:
576
- region = click.prompt("Region")
577
- validate_string_connector_param("Region", region)
578
-
579
- if not connection_name:
580
- connection_name = click.prompt(f"Connection name (optional, current: {key})", default=key)
581
- await validate_connection_name(client, connection_name, service)
582
-
583
- conn_file_name = f"{connection_name}.connection"
584
- conn_file_path = Path(getcwd(), conn_file_name)
585
-
586
- if os.path.isfile(conn_file_path):
587
- raise CLIConnectionException(FeedbackManager.error_connection_file_already_exists(name=conn_file_name))
588
-
589
- params = ConnectionReplacements.map_api_params_from_prompt_params(
590
- service, key=key, secret=secret, region=region, connection_name=connection_name
591
- )
592
-
593
- click.echo("** Creating connection...")
594
- try:
595
- _ = await client.connection_create(params)
596
- except Exception as e:
597
- raise CLIConnectionException(
598
- FeedbackManager.error_connection_create(connection_name=connection_name, error=str(e))
599
- )
600
-
601
- async with aiofiles.open(conn_file_path, "w") as f:
602
- await f.write(
603
- """TYPE gcs_hmac
604
-
605
- """
606
- )
607
- click.echo(FeedbackManager.success_connection_file_created(name=conn_file_name))
608
-
609
-
610
- @connection_create.command(name="gcs", short_help="Creates a GCS connection in the current workspace", hidden=True)
611
- @click.option("--client-id", help="Your GCS client id")
612
- @click.option("--client-email", help="Your GCS client email")
613
- @click.option("--client-x509-cert-url", help="Your GCS cert url")
614
- @click.option("--project-id", help="The GCS client project id with access to the buckets")
615
- @click.option("--private-key", help="Your GCS private key with access to the buckets")
616
- @click.option("--private-key-id", help="Your GCS private key id with access to the buckets")
617
- @click.option("--connection-name", default=None, help="The name of the connection to identify it in Tinybird")
618
- @click.option("--no-validate", is_flag=True, help="Do not validate Snowflake permissions during connection creation")
619
- @click.pass_context
620
- @coro
621
- async def connection_create_gcs(
622
- ctx: Context,
623
- client_id: Optional[str],
624
- client_email: Optional[str],
625
- client_x509_cert_url: Optional[str],
626
- project_id: Optional[str],
627
- private_key: Optional[str],
628
- private_key_id: Optional[str],
629
- connection_name: Optional[str],
630
- no_validate: Optional[bool],
631
- ) -> None:
632
- """
633
- Creates a GCS connection in the current workspace
634
-
635
- \b
636
- $ tb connection create gcs
637
- """
638
-
639
- obj: Dict[str, Any] = ctx.ensure_object(dict)
640
- client: TinyB = obj["client"]
641
- service = "gcs"
642
-
643
- if not project_id:
644
- project_id = click.prompt("Project id")
645
- validate_string_connector_param("Project id", project_id)
646
-
647
- if not private_key_id:
648
- private_key_id = click.prompt("private_key_id")
649
- validate_string_connector_param("Private key id", private_key_id)
650
-
651
- if not private_key:
652
- private_key = click.prompt("private_key")
653
- validate_string_connector_param("Private key", private_key)
654
-
655
- if not client_email:
656
- client_email = click.prompt("Client email")
657
- validate_string_connector_param("Client email", client_email)
658
-
659
- if not client_id:
660
- client_id = click.prompt("Client id")
661
- validate_string_connector_param("Client id", client_id)
662
-
663
- if not client_x509_cert_url:
664
- client_x509_cert_url = click.prompt("Client x509 cert url")
665
- validate_string_connector_param("Client x509 cert url", client_x509_cert_url)
666
-
667
- if not connection_name:
668
- connection_name = click.prompt(f"Connection name (optional, current: {client_id})", default=client_id)
669
- await validate_connection_name(client, connection_name, service)
670
-
671
- conn_file_name = f"{connection_name}.connection"
672
- conn_file_path = Path(getcwd(), conn_file_name)
673
-
674
- if os.path.isfile(conn_file_path):
675
- raise CLIConnectionException(FeedbackManager.error_connection_file_already_exists(name=conn_file_name))
676
-
677
- params = ConnectionReplacements.map_api_params_from_prompt_params(
678
- service,
679
- client_id=client_id,
680
- client_email=client_email,
681
- client_x509_cert_url=client_x509_cert_url,
682
- project_id=project_id,
683
- private_key=private_key,
684
- private_key_id=private_key_id,
685
- connection_name=connection_name,
686
- )
687
-
688
- if not no_validate:
689
- click.echo("** Validating connection...")
690
- is_connection_valid = await client.validate_preview_connection(service, params)
691
-
692
- if not is_connection_valid:
693
- raise CLIConnectionException(FeedbackManager.error_connection_improper_permissions())
694
-
695
- click.echo("** Creating connection...")
696
- try:
697
- _ = await client.connection_create(params)
698
- except Exception as e:
699
- raise CLIConnectionException(
700
- FeedbackManager.error_connection_create(connection_name=connection_name, error=str(e))
701
- )
702
-
703
- async with aiofiles.open(conn_file_path, "w") as f:
704
- await f.write(
705
- """TYPE {service}
706
-
707
- """
708
- )
709
- click.echo(FeedbackManager.success_connection_file_created(name=conn_file_name))
710
-
711
-
712
- @connection_create.command(name="s3_iamrole", short_help="Creates a AWS S3 connection using IAM role authentication")
713
- @click.option("--connection-name", default=None, help="The name of the connection to identify it in Tinybird")
714
- @click.option("--role-arn", default=None, help="The ARN of the IAM role to use for the connection")
715
- @click.option("--region", default=None, help="The Amazon S3 region where the bucket is located")
716
- @click.option("--policy", default="write", help="The Amazon S3 access policy: write or read")
717
- @click.option(
718
- "--no-validate", is_flag=True, default=False, help="Do not validate S3 permissions during connection creation"
719
- )
720
- @click.pass_context
721
- @coro
722
- async def connection_create_s3_iamrole(
723
- ctx: Context,
724
- connection_name: Optional[str] = "",
725
- role_arn: Optional[str] = "",
726
- region: Optional[str] = "",
727
- policy: str = "write",
728
- no_validate: Optional[bool] = False,
729
- ) -> None:
730
- """
731
- Creates a S3 connection using IAM role authentication in the current workspace
732
-
733
- \b
734
- $ tb connection create s3_iamrole
735
- """
736
-
737
- obj: Dict[str, Any] = ctx.ensure_object(dict)
738
- client: TinyB = obj["client"]
739
- service = DataConnectorType.AMAZON_S3_IAMROLE
740
- role_arn, region, external_id = await validate_aws_iamrole_integration(
741
- client,
742
- service=service,
743
- role_arn=role_arn,
744
- region=region,
745
- policy=policy,
746
- no_validate=no_validate,
747
- )
748
- connection_name = await validate_aws_iamrole_connection_name(client, connection_name, no_validate)
749
- await create_aws_iamrole_connection(
750
- client, service=service, connection_name=connection_name, role_arn=role_arn, region=region
751
- )
752
- if external_id:
753
- click.echo(
754
- FeedbackManager.success_s3_iam_connection_created(
755
- connection_name=connection_name, external_id=external_id, role_arn=role_arn
756
- )
757
- )
758
-
759
-
760
- @connection_create.command(
761
- name="dynamodb", short_help="Creates a AWS DynamoDB connection using IAM role authentication", hidden=True
762
- )
763
- @click.option("--connection-name", default=None, help="The name of the connection to identify it in Tinybird")
764
- @click.option("--role-arn", default=None, help="The ARN of the IAM role to use for the connection")
765
- @click.option("--region", default=None, help="The AWS region where DynamoDB is located")
766
- @click.option("--no-validate", is_flag=True, default=False, help="Do not validate DynamoDB connection during creation")
767
- @click.pass_context
768
- @coro
769
- async def connection_create_dynamodb(
770
- ctx: Context,
771
- connection_name: Optional[str] = "",
772
- role_arn: Optional[str] = "",
773
- region: Optional[str] = "",
774
- no_validate: Optional[bool] = False,
775
- ) -> None:
776
- """
777
- Creates a DynamoDB connection using IAM role authentication in the current workspace
778
-
779
- \b
780
- $ tb connection create dynamodb
781
- """
782
-
783
- obj: Dict[str, Any] = ctx.ensure_object(dict)
784
- client: TinyB = obj["client"]
785
-
786
- service = DataConnectorType.AMAZON_DYNAMODB
787
- role_arn, region, _external_id = await validate_aws_iamrole_integration(
788
- client,
789
- service=service,
790
- role_arn=role_arn,
791
- region=region,
792
- policy="read",
793
- no_validate=no_validate,
794
- )
795
- connection_name = await validate_aws_iamrole_connection_name(client, connection_name, no_validate)
796
- await create_aws_iamrole_connection(
797
- client, service=service, connection_name=connection_name, role_arn=role_arn, region=region
798
- )
799
- click.echo(
800
- FeedbackManager.success_dynamodb_connection_created(
801
- connection_name=connection_name, region=region, role_arn=role_arn
802
- )
803
- )