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.
- tinybird/context.py +1 -1
- tinybird/feedback_manager.py +6 -0
- tinybird/prompts.py +6 -0
- tinybird/sql_toolset.py +9 -2
- tinybird/tb/__cli__.py +2 -2
- tinybird/tb/cli.py +2 -2
- tinybird/tb/modules/build.py +53 -12
- tinybird/tb/modules/cli.py +7 -94
- tinybird/tb/modules/create.py +4 -4
- tinybird/tb/modules/datafile/build.py +2 -2
- tinybird/tb/modules/datafile/common.py +17 -1
- tinybird/tb/modules/datasource.py +3 -471
- tinybird/tb/modules/{deploy.py → deployment.py} +22 -12
- tinybird/tb/modules/endpoint.py +187 -0
- tinybird/tb/modules/llm.py +10 -16
- tinybird/tb/modules/llm_utils.py +87 -0
- tinybird/tb/modules/local.py +4 -1
- tinybird/tb/modules/local_common.py +2 -2
- tinybird/tb/modules/mock.py +3 -4
- tinybird/tb/modules/pipe.py +1 -254
- tinybird/tb/modules/shell.py +8 -1
- tinybird/tb/modules/test.py +2 -2
- tinybird/tb/modules/update.py +4 -4
- tinybird/tb/modules/watch.py +4 -4
- tinybird/tb/modules/workspace.py +0 -96
- tinybird/tb_cli_modules/common.py +19 -17
- {tinybird-0.0.1.dev34.dist-info → tinybird-0.0.1.dev36.dist-info}/METADATA +1 -1
- {tinybird-0.0.1.dev34.dist-info → tinybird-0.0.1.dev36.dist-info}/RECORD +31 -31
- tinybird/tb/modules/connection.py +0 -803
- {tinybird-0.0.1.dev34.dist-info → tinybird-0.0.1.dev36.dist-info}/WHEEL +0 -0
- {tinybird-0.0.1.dev34.dist-info → tinybird-0.0.1.dev36.dist-info}/entry_points.txt +0 -0
- {tinybird-0.0.1.dev34.dist-info → tinybird-0.0.1.dev36.dist-info}/top_level.txt +0 -0
|
@@ -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
|
-
)
|