tinybird 0.0.1.dev117__py3-none-any.whl → 0.0.1.dev119__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/tb/__cli__.py +2 -2
- tinybird/{client.py → tb/client.py} +1 -1
- tinybird/tb/config.py +96 -0
- tinybird/tb/modules/auth.py +1 -1
- tinybird/tb/modules/build.py +1 -1
- tinybird/tb/modules/cli.py +2 -73
- tinybird/tb/modules/common.py +114 -43
- tinybird/tb/modules/config.py +2 -2
- tinybird/tb/modules/connection.py +45 -13
- tinybird/tb/modules/copy.py +1 -1
- tinybird/tb/modules/create.py +10 -9
- tinybird/tb/modules/datafile/build.py +1 -1
- tinybird/tb/modules/datafile/build_common.py +1 -1
- tinybird/tb/modules/datafile/build_datasource.py +1 -1
- tinybird/tb/modules/datafile/build_pipe.py +1 -1
- tinybird/tb/modules/datafile/diff.py +1 -1
- tinybird/tb/modules/datafile/format_datasource.py +1 -1
- tinybird/tb/modules/datafile/playground.py +1 -1
- tinybird/tb/modules/datafile/pull.py +1 -1
- tinybird/tb/modules/datasource.py +1 -1
- tinybird/tb/modules/deployment.py +19 -15
- tinybird/tb/modules/endpoint.py +1 -1
- tinybird/tb/modules/feedback_manager.py +92 -5
- tinybird/tb/modules/infra.py +1 -1
- tinybird/tb/modules/job.py +1 -1
- tinybird/tb/modules/local_common.py +1 -1
- tinybird/tb/modules/login.py +10 -0
- tinybird/tb/modules/materialization.py +1 -1
- tinybird/tb/modules/mock.py +1 -1
- tinybird/tb/modules/pipe.py +1 -1
- tinybird/tb/modules/secret.py +1 -1
- tinybird/tb/modules/shell.py +1 -1
- tinybird/tb/modules/telemetry.py +1 -1
- tinybird/tb/modules/test.py +1 -1
- tinybird/tb/modules/tinyunit/tinyunit.py +1 -1
- tinybird/tb/modules/token.py +1 -1
- tinybird/tb/modules/workspace.py +1 -1
- tinybird/tb/modules/workspace_members.py +2 -2
- {tinybird-0.0.1.dev117.dist-info → tinybird-0.0.1.dev119.dist-info}/METADATA +1 -1
- {tinybird-0.0.1.dev117.dist-info → tinybird-0.0.1.dev119.dist-info}/RECORD +43 -44
- tinybird/__cli__.py +0 -7
- tinybird/config.py +0 -142
- {tinybird-0.0.1.dev117.dist-info → tinybird-0.0.1.dev119.dist-info}/WHEEL +0 -0
- {tinybird-0.0.1.dev117.dist-info → tinybird-0.0.1.dev119.dist-info}/entry_points.txt +0 -0
- {tinybird-0.0.1.dev117.dist-info → tinybird-0.0.1.dev119.dist-info}/top_level.txt +0 -0
tinybird/tb/__cli__.py
CHANGED
|
@@ -4,5 +4,5 @@ __description__ = 'Tinybird Command Line Tool'
|
|
|
4
4
|
__url__ = 'https://www.tinybird.co/docs/cli/introduction.html'
|
|
5
5
|
__author__ = 'Tinybird'
|
|
6
6
|
__author_email__ = 'support@tinybird.co'
|
|
7
|
-
__version__ = '0.0.1.
|
|
8
|
-
__revision__ = '
|
|
7
|
+
__version__ = '0.0.1.dev119'
|
|
8
|
+
__revision__ = 'bbe4729'
|
|
@@ -15,7 +15,7 @@ from urllib3 import Retry
|
|
|
15
15
|
|
|
16
16
|
from tinybird.ch_utils.constants import COPY_ENABLED_TABLE_FUNCTIONS
|
|
17
17
|
from tinybird.syncasync import sync_to_async
|
|
18
|
-
from tinybird.
|
|
18
|
+
from tinybird.tb.modules.telemetry import add_telemetry_event
|
|
19
19
|
|
|
20
20
|
HOST = "https://api.tinybird.co"
|
|
21
21
|
LIMIT_RETRIES = 10
|
tinybird/tb/config.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from os import environ, getcwd
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any, Dict, Optional
|
|
5
|
+
|
|
6
|
+
import aiofiles
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
from tinybird.tb import __cli__
|
|
10
|
+
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
from tinybird.tb.__cli__ import __revision__
|
|
14
|
+
except Exception:
|
|
15
|
+
__revision__ = ""
|
|
16
|
+
|
|
17
|
+
DEFAULT_API_HOST = "https://api.tinybird.co"
|
|
18
|
+
DEFAULT_LOCALHOST = "http://localhost:8001"
|
|
19
|
+
CURRENT_VERSION = f"{__cli__.__version__}"
|
|
20
|
+
VERSION = f"{__cli__.__version__} (rev {__revision__})"
|
|
21
|
+
DEFAULT_UI_HOST = "https://cloud.tinybird.co"
|
|
22
|
+
SUPPORTED_CONNECTORS = ["bigquery", "snowflake"]
|
|
23
|
+
PROJECT_PATHS = ["datasources", "datasources/fixtures", "endpoints", "pipes", "tests", "scripts", "deploy"]
|
|
24
|
+
DEPRECATED_PROJECT_PATHS = ["endpoints"]
|
|
25
|
+
MIN_WORKSPACE_ID_LENGTH = 36
|
|
26
|
+
|
|
27
|
+
CLOUD_HOSTS = {
|
|
28
|
+
"https://api.tinybird.co": "https://cloud.tinybird.co/gcp/europe-west3",
|
|
29
|
+
"https://api.us-east.tinybird.co": "https://cloud.tinybird.co/gcp/us-east4",
|
|
30
|
+
"https://api.us-east.aws.tinybird.co": "https://cloud.tinybird.co/aws/us-east-1",
|
|
31
|
+
"https://api.us-west-2.aws.tinybird.co": "https://cloud.tinybird.co/aws/us-west-2",
|
|
32
|
+
"https://api.eu-central-1.aws.tinybird.co": "https://cloud.tinybird.co/aws/eu-central-1",
|
|
33
|
+
"https://api.eu-west-1.aws.tinybird.co": "https://cloud.tinybird.co/aws/eu-west-1",
|
|
34
|
+
"https://api.europe-west2.gcp.tinybird.co": "https://cloud.tinybird.co/gcp/europe-west2",
|
|
35
|
+
"https://api.ap-east.aws.tinybird.co": "https://cloud.tinybird.co/aws/ap-east",
|
|
36
|
+
"https://ui.tinybird.co": "https://cloud.tinybird.co/gcp/europe-west3",
|
|
37
|
+
"https://ui.us-east.tinybird.co": "https://cloud.tinybird.co/gcp/us-east4",
|
|
38
|
+
"https://ui.us-east.aws.tinybird.co": "https://cloud.tinybird.co/aws/us-east-1",
|
|
39
|
+
"https://ui.us-west-2.aws.tinybird.co": "https://cloud.tinybird.co/aws/us-west-2",
|
|
40
|
+
"https://ui.eu-central-1.aws.tinybird.co": "https://cloud.tinybird.co/aws/eu-central-1",
|
|
41
|
+
"https://ui.europe-west2.gcp.tinybird.co": "https://cloud.tinybird.co/gcp/europe-west2",
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
async def get_config(
|
|
46
|
+
host: str, token: Optional[str], semver: Optional[str] = None, config_file: Optional[str] = None
|
|
47
|
+
) -> Dict[str, Any]:
|
|
48
|
+
if host:
|
|
49
|
+
host = host.rstrip("/")
|
|
50
|
+
|
|
51
|
+
config = {}
|
|
52
|
+
try:
|
|
53
|
+
async with aiofiles.open(config_file or Path(getcwd()) / ".tinyb") as file:
|
|
54
|
+
res = await file.read()
|
|
55
|
+
config = json.loads(res)
|
|
56
|
+
except OSError:
|
|
57
|
+
pass
|
|
58
|
+
except json.decoder.JSONDecodeError:
|
|
59
|
+
click.echo(FeedbackManager.error_load_file_config(config_file=config_file))
|
|
60
|
+
return config
|
|
61
|
+
|
|
62
|
+
config["token_passed"] = token
|
|
63
|
+
config["token"] = token or config.get("token", None)
|
|
64
|
+
config["semver"] = semver or config.get("semver", None)
|
|
65
|
+
config["host"] = host or config.get("host", DEFAULT_API_HOST)
|
|
66
|
+
config["workspaces"] = config.get("workspaces", [])
|
|
67
|
+
config["cwd"] = config.get("cwd", getcwd())
|
|
68
|
+
return config
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
async def write_config(config: Dict[str, Any], dest_file: str = ".tinyb"):
|
|
72
|
+
config_file = Path(getcwd()) / dest_file
|
|
73
|
+
async with aiofiles.open(config_file, "w") as file:
|
|
74
|
+
await file.write(json.dumps(config, indent=4, sort_keys=True))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def get_display_cloud_host(api_host: str) -> str:
|
|
78
|
+
is_local = "localhost" in api_host
|
|
79
|
+
if is_local:
|
|
80
|
+
port = api_host.split(":")[-1]
|
|
81
|
+
return f"http://cloud.tinybird.co/local/{port}"
|
|
82
|
+
return CLOUD_HOSTS.get(api_host, api_host)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class FeatureFlags:
|
|
86
|
+
@classmethod
|
|
87
|
+
def ignore_sql_errors(cls) -> bool: # Context: #1155
|
|
88
|
+
return "TB_IGNORE_SQL_ERRORS" in environ
|
|
89
|
+
|
|
90
|
+
@classmethod
|
|
91
|
+
def is_localhost(cls) -> bool:
|
|
92
|
+
return "SET_LOCALHOST" in environ
|
|
93
|
+
|
|
94
|
+
@classmethod
|
|
95
|
+
def enable_snowflake_connector_command(cls) -> bool:
|
|
96
|
+
return "ENABLE_SNOWFLAKE_CONNECTOR_COMMAND" in environ
|
tinybird/tb/modules/auth.py
CHANGED
|
@@ -9,7 +9,7 @@ from typing import Any, Dict, List, Optional
|
|
|
9
9
|
import click
|
|
10
10
|
import humanfriendly.tables
|
|
11
11
|
|
|
12
|
-
from tinybird.config import get_display_cloud_host
|
|
12
|
+
from tinybird.tb.config import get_display_cloud_host
|
|
13
13
|
from tinybird.tb.modules.cli import cli
|
|
14
14
|
from tinybird.tb.modules.common import (
|
|
15
15
|
configure_connector,
|
tinybird/tb/modules/build.py
CHANGED
|
@@ -14,7 +14,7 @@ import click
|
|
|
14
14
|
import requests
|
|
15
15
|
|
|
16
16
|
import tinybird.context as context
|
|
17
|
-
from tinybird.client import TinyB
|
|
17
|
+
from tinybird.tb.client import TinyB
|
|
18
18
|
from tinybird.tb.modules.cli import cli
|
|
19
19
|
from tinybird.tb.modules.common import push_data
|
|
20
20
|
from tinybird.tb.modules.config import CLIConfig
|
tinybird/tb/modules/cli.py
CHANGED
|
@@ -17,25 +17,22 @@ import click
|
|
|
17
17
|
import humanfriendly
|
|
18
18
|
from click import Context
|
|
19
19
|
|
|
20
|
-
from tinybird.
|
|
20
|
+
from tinybird.tb import __cli__
|
|
21
|
+
from tinybird.tb.client import (
|
|
21
22
|
AuthException,
|
|
22
23
|
AuthNoTokenException,
|
|
23
|
-
TinyB,
|
|
24
24
|
)
|
|
25
|
-
from tinybird.tb import __cli__
|
|
26
25
|
from tinybird.tb.modules.common import (
|
|
27
26
|
CatchAuthExceptions,
|
|
28
27
|
CLIException,
|
|
29
28
|
_get_tb_client,
|
|
30
29
|
coro,
|
|
31
30
|
echo_safe_format_table,
|
|
32
|
-
get_current_main_workspace,
|
|
33
31
|
getenv_bool,
|
|
34
32
|
try_update_config_with_remote,
|
|
35
33
|
)
|
|
36
34
|
from tinybird.tb.modules.config import CLIConfig
|
|
37
35
|
from tinybird.tb.modules.datafile.build import build_graph
|
|
38
|
-
from tinybird.tb.modules.datafile.diff import diff_command
|
|
39
36
|
from tinybird.tb.modules.datafile.pull import folder_pull
|
|
40
37
|
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
41
38
|
from tinybird.tb.modules.local_common import get_tinybird_local_client
|
|
@@ -165,74 +162,6 @@ async def pull(ctx: Context, force: bool, fmt: bool) -> None:
|
|
|
165
162
|
return await folder_pull(client, project.path, force, fmt=fmt)
|
|
166
163
|
|
|
167
164
|
|
|
168
|
-
@cli.command(
|
|
169
|
-
name="diff",
|
|
170
|
-
short_help="Diff local datafiles to the corresponding remote files in the workspace. Only diffs VERSION and SCHEMA for .datasource files.",
|
|
171
|
-
)
|
|
172
|
-
@click.argument("filename", type=click.Path(exists=True), nargs=-1, required=False)
|
|
173
|
-
@click.option(
|
|
174
|
-
"--fmt/--no-fmt",
|
|
175
|
-
is_flag=True,
|
|
176
|
-
default=True,
|
|
177
|
-
help="Format files before doing the diff, default is True so both files match the format",
|
|
178
|
-
)
|
|
179
|
-
@click.option("--no-color", is_flag=True, default=False, help="Don't colorize diff")
|
|
180
|
-
@click.option(
|
|
181
|
-
"--no-verbose", is_flag=True, default=False, help="List the resources changed not the content of the diff"
|
|
182
|
-
)
|
|
183
|
-
@click.option(
|
|
184
|
-
"--main",
|
|
185
|
-
is_flag=True,
|
|
186
|
-
default=False,
|
|
187
|
-
help="Diff local datafiles to the corresponding remote files in the main workspace. Only works when authenticated on a Branch.",
|
|
188
|
-
hidden=True,
|
|
189
|
-
)
|
|
190
|
-
@click.pass_context
|
|
191
|
-
@coro
|
|
192
|
-
async def diff(
|
|
193
|
-
ctx: Context, filename: Optional[Tuple], fmt: bool, no_color: bool, no_verbose: bool, main: bool
|
|
194
|
-
) -> None:
|
|
195
|
-
only_resources_changed = no_verbose
|
|
196
|
-
client: TinyB = ctx.ensure_object(dict)["client"]
|
|
197
|
-
|
|
198
|
-
if not main:
|
|
199
|
-
changed = await diff_command(
|
|
200
|
-
list(filename) if filename else None, fmt, client, no_color, with_print=not only_resources_changed
|
|
201
|
-
)
|
|
202
|
-
else:
|
|
203
|
-
config = CLIConfig.get_project_config()
|
|
204
|
-
|
|
205
|
-
response = await client.user_workspaces_and_branches(version="v1")
|
|
206
|
-
ws_client = None
|
|
207
|
-
for workspace in response["workspaces"]:
|
|
208
|
-
if config["id"] == workspace["id"]:
|
|
209
|
-
if not workspace.get("is_branch"):
|
|
210
|
-
raise CLIException(FeedbackManager.error_not_a_branch())
|
|
211
|
-
|
|
212
|
-
origin = workspace["main"]
|
|
213
|
-
workspace = await get_current_main_workspace(config)
|
|
214
|
-
|
|
215
|
-
if not workspace:
|
|
216
|
-
raise CLIException(FeedbackManager.error_workspace(workspace=origin))
|
|
217
|
-
|
|
218
|
-
ws_client = _get_tb_client(workspace["token"], config["host"])
|
|
219
|
-
break
|
|
220
|
-
|
|
221
|
-
if not ws_client:
|
|
222
|
-
raise CLIException(FeedbackManager.error_workspace(workspace=origin))
|
|
223
|
-
changed = await diff_command(
|
|
224
|
-
list(filename) if filename else None, fmt, ws_client, no_color, with_print=not only_resources_changed
|
|
225
|
-
)
|
|
226
|
-
|
|
227
|
-
if only_resources_changed:
|
|
228
|
-
click.echo("\n")
|
|
229
|
-
for resource, status in dict(sorted(changed.items(), key=lambda item: str(item[1]))).items():
|
|
230
|
-
if status is None:
|
|
231
|
-
continue
|
|
232
|
-
status = "changed" if status not in ["remote", "local", "shared"] else status
|
|
233
|
-
click.echo(f"{status}: {resource}")
|
|
234
|
-
|
|
235
|
-
|
|
236
165
|
@cli.command()
|
|
237
166
|
@click.argument("query", required=False)
|
|
238
167
|
@click.option("--rows_limit", default=100, help="Max number of rows retrieved")
|
tinybird/tb/modules/common.py
CHANGED
|
@@ -35,7 +35,7 @@ from humanfriendly.tables import format_pretty_table
|
|
|
35
35
|
from packaging.version import Version
|
|
36
36
|
from thefuzz import process
|
|
37
37
|
|
|
38
|
-
from tinybird.client import (
|
|
38
|
+
from tinybird.tb.client import (
|
|
39
39
|
AuthException,
|
|
40
40
|
AuthNoTokenException,
|
|
41
41
|
DoesNotExistException,
|
|
@@ -43,7 +43,7 @@ from tinybird.client import (
|
|
|
43
43
|
OperationCanNotBePerformed,
|
|
44
44
|
TinyB,
|
|
45
45
|
)
|
|
46
|
-
from tinybird.config import (
|
|
46
|
+
from tinybird.tb.config import (
|
|
47
47
|
DEFAULT_API_HOST,
|
|
48
48
|
DEFAULT_UI_HOST,
|
|
49
49
|
SUPPORTED_CONNECTORS,
|
|
@@ -518,7 +518,10 @@ def get_region_info(ctx, region=None):
|
|
|
518
518
|
api_host = format_host(
|
|
519
519
|
region["api_host"] if region else ctx.obj["config"].get("host", DEFAULT_API_HOST), subdomain="api"
|
|
520
520
|
)
|
|
521
|
-
ui_host = format_host(
|
|
521
|
+
ui_host = format_host(
|
|
522
|
+
region["host"] if region else ctx.obj["config"].get("host", DEFAULT_UI_HOST), subdomain="cloud"
|
|
523
|
+
)
|
|
524
|
+
|
|
522
525
|
return name, api_host, ui_host
|
|
523
526
|
|
|
524
527
|
|
|
@@ -553,13 +556,13 @@ def format_host(host: str, subdomain: Optional[str] = None) -> str:
|
|
|
553
556
|
if subdomain and not is_localhost:
|
|
554
557
|
url_info = urlparse(host)
|
|
555
558
|
current_subdomain = url_info.netloc.split(".")[0]
|
|
556
|
-
if current_subdomain in ("api", "ui"):
|
|
559
|
+
if current_subdomain in ("api", "ui", "app", "cloud"):
|
|
557
560
|
host = host.replace(current_subdomain, subdomain)
|
|
558
561
|
if "localhost" in host or is_localhost:
|
|
559
562
|
host = f"http://{host}" if "http" not in host else host
|
|
560
563
|
elif not host.startswith("http"):
|
|
561
564
|
host = f"https://{host}"
|
|
562
|
-
return host
|
|
565
|
+
return host.replace("app.tinybird.co", "cloud.tinybird.co")
|
|
563
566
|
|
|
564
567
|
|
|
565
568
|
def region_from_host(region_name_or_host, regions):
|
|
@@ -1506,7 +1509,7 @@ async def try_authenticate(
|
|
|
1506
1509
|
ui_host: str
|
|
1507
1510
|
token: Optional[str]
|
|
1508
1511
|
if host and not selected_region:
|
|
1509
|
-
name, api_host, ui_host = (host, format_host(host, subdomain="api"), format_host(host, subdomain="
|
|
1512
|
+
name, api_host, ui_host = (host, format_host(host, subdomain="api"), format_host(host, subdomain="cloud"))
|
|
1510
1513
|
token = config.get_token()
|
|
1511
1514
|
else:
|
|
1512
1515
|
name, api_host, ui_host = get_region_info(config, selected_region)
|
|
@@ -1754,27 +1757,26 @@ async def remove_release(
|
|
|
1754
1757
|
async def run_aws_iamrole_connection_flow(
|
|
1755
1758
|
client: TinyB,
|
|
1756
1759
|
service: str,
|
|
1757
|
-
|
|
1758
|
-
):
|
|
1760
|
+
environment: str,
|
|
1761
|
+
) -> Tuple[str, str, str]:
|
|
1759
1762
|
if service == DataConnectorType.AMAZON_DYNAMODB:
|
|
1760
|
-
raise NotImplementedError("DynamoDB is not supported
|
|
1763
|
+
raise NotImplementedError("DynamoDB is not supported")
|
|
1761
1764
|
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
+
bucket_name = click.prompt(
|
|
1766
|
+
"📦 Bucket name (specific name recommended, use '*' for unrestricted access in IAM policy)",
|
|
1767
|
+
prompt_suffix="\n> ",
|
|
1768
|
+
)
|
|
1769
|
+
validate_string_connector_param("Bucket", bucket_name)
|
|
1765
1770
|
|
|
1766
|
-
|
|
1767
|
-
validate_string_connector_param("Region",
|
|
1771
|
+
region = click.prompt("🌐 Region (the region where the bucket is located, e.g. 'us-east-1')", prompt_suffix="\n> ")
|
|
1772
|
+
validate_string_connector_param("Region", region)
|
|
1768
1773
|
|
|
1769
|
-
access_policy, trust_policy,
|
|
1770
|
-
access_policy = access_policy.replace("<bucket>",
|
|
1774
|
+
access_policy, trust_policy, _ = await get_aws_iamrole_policies(client, service=service, policy="read")
|
|
1775
|
+
access_policy = access_policy.replace("<bucket>", bucket_name)
|
|
1771
1776
|
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
prompt_suffix="Press y to continue:",
|
|
1776
|
-
):
|
|
1777
|
-
sys.exit(1)
|
|
1777
|
+
click.echo(FeedbackManager.prompt_s3_iamrole_connection_login_aws())
|
|
1778
|
+
click.echo(FeedbackManager.click_enter_to_continue())
|
|
1779
|
+
input()
|
|
1778
1780
|
|
|
1779
1781
|
access_policy_copied = True
|
|
1780
1782
|
try:
|
|
@@ -1782,16 +1784,58 @@ async def run_aws_iamrole_connection_flow(
|
|
|
1782
1784
|
except Exception:
|
|
1783
1785
|
access_policy_copied = False
|
|
1784
1786
|
|
|
1785
|
-
|
|
1787
|
+
# Display message and wait for user to press Enter
|
|
1788
|
+
click.echo(
|
|
1789
|
+
FeedbackManager.prompt_s3_iamrole_connection_policy(
|
|
1790
|
+
access_policy=access_policy, aws_region=region, bucket=bucket_name
|
|
1791
|
+
)
|
|
1792
|
+
if access_policy_copied
|
|
1793
|
+
else FeedbackManager.prompt_s3_iamrole_connection_policy_not_copied(
|
|
1794
|
+
access_policy=access_policy, aws_region=region, bucket=bucket_name
|
|
1795
|
+
)
|
|
1796
|
+
)
|
|
1797
|
+
click.echo(FeedbackManager.click_enter_to_continue())
|
|
1798
|
+
input()
|
|
1799
|
+
|
|
1800
|
+
trust_policy_copied = True
|
|
1801
|
+
try:
|
|
1802
|
+
pyperclip.copy(trust_policy)
|
|
1803
|
+
except Exception:
|
|
1804
|
+
trust_policy_copied = False
|
|
1805
|
+
|
|
1806
|
+
role_arn = click.prompt(
|
|
1786
1807
|
(
|
|
1787
|
-
FeedbackManager.
|
|
1788
|
-
|
|
1789
|
-
|
|
1808
|
+
FeedbackManager.prompt_s3_iamrole_connection_role(
|
|
1809
|
+
trust_policy=trust_policy,
|
|
1810
|
+
aws_region=region,
|
|
1811
|
+
bucket=bucket_name,
|
|
1812
|
+
environment=environment.upper(),
|
|
1813
|
+
step="3",
|
|
1814
|
+
)
|
|
1815
|
+
if trust_policy_copied
|
|
1816
|
+
else FeedbackManager.prompt_s3_iamrole_connection_role_not_copied(
|
|
1817
|
+
trust_policy=trust_policy,
|
|
1818
|
+
aws_region=region,
|
|
1819
|
+
bucket=bucket_name,
|
|
1820
|
+
environment=environment.upper(),
|
|
1821
|
+
step="3",
|
|
1822
|
+
)
|
|
1790
1823
|
),
|
|
1791
1824
|
show_default=False,
|
|
1792
|
-
|
|
1793
|
-
)
|
|
1794
|
-
|
|
1825
|
+
)
|
|
1826
|
+
validate_string_connector_param("Role ARN", role_arn)
|
|
1827
|
+
|
|
1828
|
+
return role_arn, region, bucket_name
|
|
1829
|
+
|
|
1830
|
+
|
|
1831
|
+
async def production_aws_iamrole_only(
|
|
1832
|
+
prod_client: TinyB,
|
|
1833
|
+
service: str,
|
|
1834
|
+
region: str,
|
|
1835
|
+
bucket_name: str,
|
|
1836
|
+
environment: str,
|
|
1837
|
+
) -> Tuple[str, str, str]:
|
|
1838
|
+
_, trust_policy, external_id = await get_aws_iamrole_policies(prod_client, service=service, policy="read")
|
|
1795
1839
|
|
|
1796
1840
|
trust_policy_copied = True
|
|
1797
1841
|
try:
|
|
@@ -1799,21 +1843,29 @@ async def run_aws_iamrole_connection_flow(
|
|
|
1799
1843
|
except Exception:
|
|
1800
1844
|
trust_policy_copied = False
|
|
1801
1845
|
|
|
1802
|
-
|
|
1846
|
+
role_arn = click.prompt(
|
|
1803
1847
|
(
|
|
1804
|
-
FeedbackManager.prompt_s3_iamrole_connection_role(
|
|
1848
|
+
FeedbackManager.prompt_s3_iamrole_connection_role(
|
|
1849
|
+
trust_policy=trust_policy,
|
|
1850
|
+
aws_region=region,
|
|
1851
|
+
bucket=bucket_name,
|
|
1852
|
+
environment=environment.upper(),
|
|
1853
|
+
step="4",
|
|
1854
|
+
)
|
|
1805
1855
|
if trust_policy_copied
|
|
1806
|
-
else FeedbackManager.prompt_s3_iamrole_connection_role_not_copied(
|
|
1856
|
+
else FeedbackManager.prompt_s3_iamrole_connection_role_not_copied(
|
|
1857
|
+
trust_policy=trust_policy,
|
|
1858
|
+
aws_region=region,
|
|
1859
|
+
bucket=bucket_name,
|
|
1860
|
+
environment=environment.upper(),
|
|
1861
|
+
step="4",
|
|
1862
|
+
)
|
|
1807
1863
|
),
|
|
1808
1864
|
show_default=False,
|
|
1809
|
-
|
|
1810
|
-
):
|
|
1811
|
-
sys.exit(1)
|
|
1812
|
-
|
|
1813
|
-
role_arn = click.prompt("Enter the ARN of the role you just created")
|
|
1865
|
+
)
|
|
1814
1866
|
validate_string_connector_param("Role ARN", role_arn)
|
|
1815
1867
|
|
|
1816
|
-
return role_arn,
|
|
1868
|
+
return role_arn, region, external_id
|
|
1817
1869
|
|
|
1818
1870
|
|
|
1819
1871
|
async def get_aws_iamrole_policies(client: TinyB, service: str, policy: str = "write"):
|
|
@@ -1846,16 +1898,35 @@ async def get_aws_iamrole_policies(client: TinyB, service: str, policy: str = "w
|
|
|
1846
1898
|
return json.dumps(access_policy, indent=4), json.dumps(trust_policy, indent=4), external_id
|
|
1847
1899
|
|
|
1848
1900
|
|
|
1849
|
-
|
|
1901
|
+
def get_s3_connection_name(project_folder: str) -> str:
|
|
1850
1902
|
connection_name = None
|
|
1903
|
+
valid_pattern = r"^[a-zA-Z][a-zA-Z0-9_]*$"
|
|
1904
|
+
|
|
1851
1905
|
while not connection_name:
|
|
1852
|
-
connection_name = click.prompt(
|
|
1906
|
+
connection_name = click.prompt(
|
|
1907
|
+
"🔗 Enter a name for your new Tinybird S3 connection (use alphanumeric characters, and underscores)",
|
|
1908
|
+
prompt_suffix="\n> ",
|
|
1909
|
+
show_default=True,
|
|
1910
|
+
)
|
|
1853
1911
|
assert isinstance(connection_name, str)
|
|
1854
1912
|
|
|
1855
|
-
|
|
1856
|
-
|
|
1913
|
+
# Validate against invalid characters
|
|
1914
|
+
if not re.match(valid_pattern, connection_name):
|
|
1915
|
+
if not connection_name[0].isalpha():
|
|
1916
|
+
click.echo("Error: Connection name must start with a letter.")
|
|
1917
|
+
else:
|
|
1918
|
+
click.echo("Error: Connection name can only contain letters, numbers, and underscores.")
|
|
1857
1919
|
connection_name = None
|
|
1858
|
-
|
|
1920
|
+
continue
|
|
1921
|
+
|
|
1922
|
+
# Check for existing connection with the same name
|
|
1923
|
+
project_folder_path = Path(project_folder)
|
|
1924
|
+
connection_files = list(project_folder_path.glob("*.connection"))
|
|
1925
|
+
for conn_file in connection_files:
|
|
1926
|
+
if conn_file.stem == connection_name:
|
|
1927
|
+
click.echo(FeedbackManager.error_connection_file_already_exists(name=f"{connection_name}.connection"))
|
|
1928
|
+
connection_name = None
|
|
1929
|
+
break
|
|
1859
1930
|
return connection_name
|
|
1860
1931
|
|
|
1861
1932
|
|
tinybird/tb/modules/config.py
CHANGED
|
@@ -8,8 +8,8 @@ from urllib.parse import urlparse
|
|
|
8
8
|
|
|
9
9
|
from packaging import version
|
|
10
10
|
|
|
11
|
-
import tinybird.client as tbc
|
|
12
|
-
from tinybird.config import CURRENT_VERSION, DEFAULT_API_HOST, DEFAULT_LOCALHOST
|
|
11
|
+
import tinybird.tb.client as tbc
|
|
12
|
+
from tinybird.tb.config import CURRENT_VERSION, DEFAULT_API_HOST, DEFAULT_LOCALHOST
|
|
13
13
|
|
|
14
14
|
APP_CONFIG_NAME = "tinybird"
|
|
15
15
|
|
|
@@ -3,22 +3,24 @@
|
|
|
3
3
|
# - If it makes sense and only when strictly necessary, you can create utility functions in this file.
|
|
4
4
|
# - But please, **do not** interleave utility functions and command definitions.
|
|
5
5
|
|
|
6
|
+
import uuid
|
|
6
7
|
from typing import Any, Dict, List, Optional
|
|
7
8
|
|
|
8
9
|
import click
|
|
9
10
|
from click import Context
|
|
10
11
|
|
|
11
|
-
from tinybird.client import TinyB
|
|
12
|
+
from tinybird.tb.client import TinyB
|
|
12
13
|
from tinybird.tb.modules.cli import cli
|
|
13
14
|
from tinybird.tb.modules.common import (
|
|
14
15
|
DataConnectorType,
|
|
15
16
|
_get_setting_value,
|
|
16
17
|
coro,
|
|
17
18
|
echo_safe_humanfriendly_tables_format_smart_table,
|
|
19
|
+
get_s3_connection_name,
|
|
20
|
+
production_aws_iamrole_only,
|
|
18
21
|
run_aws_iamrole_connection_flow,
|
|
19
|
-
validate_aws_iamrole_connection_name,
|
|
20
22
|
)
|
|
21
|
-
from tinybird.tb.modules.create import
|
|
23
|
+
from tinybird.tb.modules.create import generate_aws_iamrole_connection_file_with_secret
|
|
22
24
|
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
23
25
|
from tinybird.tb.modules.project import Project
|
|
24
26
|
|
|
@@ -148,20 +150,50 @@ async def connection_create_s3(ctx: Context) -> None:
|
|
|
148
150
|
project: Project = ctx.ensure_object(dict)["project"]
|
|
149
151
|
obj: Dict[str, Any] = ctx.ensure_object(dict)
|
|
150
152
|
client: TinyB = obj["client"]
|
|
153
|
+
|
|
151
154
|
service = DataConnectorType.AMAZON_S3
|
|
152
|
-
|
|
153
|
-
|
|
155
|
+
click.echo(FeedbackManager.prompt_s3_connection_header())
|
|
156
|
+
connection_name = get_s3_connection_name(project.folder)
|
|
157
|
+
role_arn, region, bucket_name = await run_aws_iamrole_connection_flow(
|
|
154
158
|
client,
|
|
155
159
|
service=service,
|
|
156
|
-
|
|
160
|
+
environment=obj["env"],
|
|
161
|
+
)
|
|
162
|
+
unique_suffix = uuid.uuid4().hex[:8] # Use first 8 chars of a UUID for brevity
|
|
163
|
+
secret_name = f"s3_role_arn_{connection_name}_{unique_suffix}"
|
|
164
|
+
await client.create_secret(name=secret_name, value=role_arn)
|
|
165
|
+
|
|
166
|
+
create_in_cloud = (
|
|
167
|
+
click.confirm(FeedbackManager.prompt_s3_iamrole_success_cloud(), default=True)
|
|
168
|
+
if obj["env"] == "local"
|
|
169
|
+
else False
|
|
157
170
|
)
|
|
158
171
|
|
|
159
|
-
|
|
160
|
-
|
|
172
|
+
if create_in_cloud:
|
|
173
|
+
prod_config = obj["config"]
|
|
174
|
+
host = prod_config["host"]
|
|
175
|
+
token = prod_config["token"]
|
|
176
|
+
prod_client = TinyB(
|
|
177
|
+
token=token,
|
|
178
|
+
host=host,
|
|
179
|
+
staging=False,
|
|
180
|
+
)
|
|
181
|
+
prod_role_arn, _, _ = await production_aws_iamrole_only(
|
|
182
|
+
prod_client, service=service, region=region, bucket_name=bucket_name, environment="cloud"
|
|
183
|
+
)
|
|
184
|
+
await prod_client.create_secret(name=secret_name, value=prod_role_arn)
|
|
185
|
+
|
|
186
|
+
connection_file_path = await generate_aws_iamrole_connection_file_with_secret(
|
|
187
|
+
name=connection_name,
|
|
188
|
+
service=service,
|
|
189
|
+
role_arn_secret_name=secret_name,
|
|
190
|
+
region=region,
|
|
191
|
+
folder=project.folder,
|
|
161
192
|
)
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
)
|
|
193
|
+
|
|
194
|
+
click.echo(
|
|
195
|
+
FeedbackManager.prompt_s3_iamrole_success(
|
|
196
|
+
connection_name=connection_name,
|
|
197
|
+
connection_path=str(connection_file_path),
|
|
167
198
|
)
|
|
199
|
+
)
|
tinybird/tb/modules/copy.py
CHANGED
|
@@ -10,7 +10,7 @@ from typing import Optional, Tuple
|
|
|
10
10
|
import click
|
|
11
11
|
from click import Context
|
|
12
12
|
|
|
13
|
-
from tinybird.client import AuthNoTokenException, TinyB
|
|
13
|
+
from tinybird.tb.client import AuthNoTokenException, TinyB
|
|
14
14
|
from tinybird.tb.modules.cli import cli
|
|
15
15
|
from tinybird.tb.modules.common import coro, echo_safe_humanfriendly_tables_format_smart_table, wait_job
|
|
16
16
|
from tinybird.tb.modules.datafile.common import get_name_version
|
tinybird/tb/modules/create.py
CHANGED
|
@@ -5,8 +5,8 @@ from typing import Optional, Tuple
|
|
|
5
5
|
|
|
6
6
|
import click
|
|
7
7
|
|
|
8
|
-
from tinybird.client import TinyB
|
|
9
8
|
from tinybird.prompts import create_prompt, mock_prompt, readme_prompt, rules_prompt
|
|
9
|
+
from tinybird.tb.client import TinyB
|
|
10
10
|
from tinybird.tb.modules.cicd import init_cicd
|
|
11
11
|
from tinybird.tb.modules.cli import cli
|
|
12
12
|
from tinybird.tb.modules.common import _generate_datafile, coro, generate_datafile
|
|
@@ -362,26 +362,27 @@ def generate_pipe_file(name: str, content: str, folder: str) -> Path:
|
|
|
362
362
|
return f.relative_to(folder)
|
|
363
363
|
|
|
364
364
|
|
|
365
|
-
def generate_connection_file(name: str, content: str, folder: str) -> Path:
|
|
365
|
+
def generate_connection_file(name: str, content: str, folder: str, skip_feedback: bool = False) -> Path:
|
|
366
366
|
base = Path(folder) / "connections"
|
|
367
367
|
if not base.exists():
|
|
368
368
|
base.mkdir()
|
|
369
369
|
f = base / (f"{name}.connection")
|
|
370
370
|
with open(f"{f}", "w") as file:
|
|
371
371
|
file.write(content)
|
|
372
|
-
|
|
372
|
+
if not skip_feedback:
|
|
373
|
+
click.echo(FeedbackManager.info_file_created(file=f.relative_to(folder)))
|
|
373
374
|
return f.relative_to(folder)
|
|
374
375
|
|
|
375
376
|
|
|
376
|
-
async def
|
|
377
|
-
name: str, service: str,
|
|
378
|
-
) ->
|
|
377
|
+
async def generate_aws_iamrole_connection_file_with_secret(
|
|
378
|
+
name: str, service: str, role_arn_secret_name: str, region: str, folder: str
|
|
379
|
+
) -> Path:
|
|
379
380
|
content = f"""TYPE {service}
|
|
380
|
-
|
|
381
|
-
S3_ARN {role_arn}
|
|
381
|
+
S3_ARN {{{{ tb_secret("{role_arn_secret_name}") }}}}
|
|
382
382
|
S3_REGION {region}
|
|
383
383
|
"""
|
|
384
|
-
generate_connection_file(name, content, folder)
|
|
384
|
+
file_path = generate_connection_file(name, content, folder, skip_feedback=True)
|
|
385
|
+
return file_path
|
|
385
386
|
|
|
386
387
|
|
|
387
388
|
def create_rules(folder: str, source: str, agent: str):
|
|
@@ -11,9 +11,9 @@ from typing import Any, Dict, Iterable, List, Optional, Set, Tuple, Union
|
|
|
11
11
|
import click
|
|
12
12
|
from toposort import toposort
|
|
13
13
|
|
|
14
|
-
from tinybird.client import TinyB
|
|
15
14
|
from tinybird.sql import parse_table_structure, schema_to_sql_columns
|
|
16
15
|
from tinybird.sql_template import get_used_tables_in_template, render_sql_template
|
|
16
|
+
from tinybird.tb.client import TinyB
|
|
17
17
|
from tinybird.tb.modules.common import get_ca_pem_content
|
|
18
18
|
from tinybird.tb.modules.datafile.build_datasource import is_datasource
|
|
19
19
|
from tinybird.tb.modules.datafile.build_pipe import (
|
|
@@ -5,7 +5,7 @@ from typing import Any, Dict, List, Optional
|
|
|
5
5
|
|
|
6
6
|
import click
|
|
7
7
|
|
|
8
|
-
from tinybird.client import DoesNotExistException, TinyB
|
|
8
|
+
from tinybird.tb.client import DoesNotExistException, TinyB
|
|
9
9
|
from tinybird.tb.modules.datafile.common import PREVIEW_CONNECTOR_SERVICES, ImportReplacements
|
|
10
10
|
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
11
11
|
|