tinybird 0.0.1.dev97__py3-none-any.whl → 0.0.1.dev98__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/client.py +27 -11
- tinybird/tb/__cli__.py +2 -2
- tinybird/tb/modules/cli.py +2 -2
- tinybird/tb/modules/common.py +10 -8
- tinybird/tb/modules/config.py +1 -1
- tinybird/tb/modules/datafile/build.py +1 -1
- tinybird/tb/modules/datafile/build_datasource.py +2 -2
- tinybird/tb/modules/datafile/build_pipe.py +2 -2
- tinybird/tb/modules/datafile/playground.py +1 -1
- tinybird/tb/modules/infra.py +441 -255
- tinybird/tb/modules/local_common.py +5 -3
- tinybird/tb/modules/login.py +6 -7
- tinybird/tb/modules/workspace.py +2 -2
- {tinybird-0.0.1.dev97.dist-info → tinybird-0.0.1.dev98.dist-info}/METADATA +1 -1
- {tinybird-0.0.1.dev97.dist-info → tinybird-0.0.1.dev98.dist-info}/RECORD +18 -18
- {tinybird-0.0.1.dev97.dist-info → tinybird-0.0.1.dev98.dist-info}/WHEEL +0 -0
- {tinybird-0.0.1.dev97.dist-info → tinybird-0.0.1.dev98.dist-info}/entry_points.txt +0 -0
- {tinybird-0.0.1.dev97.dist-info → tinybird-0.0.1.dev98.dist-info}/top_level.txt +0 -0
tinybird/client.py
CHANGED
|
@@ -714,8 +714,8 @@ class TinyB:
|
|
|
714
714
|
async def job_cancel(self, job_id: str):
|
|
715
715
|
return await self._req(f"/v0/jobs/{job_id}/cancel", method="POST", data=b"")
|
|
716
716
|
|
|
717
|
-
async def user_workspaces(self):
|
|
718
|
-
data = await self._req("/
|
|
717
|
+
async def user_workspaces(self, version: str = "v0"):
|
|
718
|
+
data = await self._req(f"/{version}/user/workspaces/?with_environments=false")
|
|
719
719
|
# TODO: this is repeated in local_common.py but I'm avoiding circular imports
|
|
720
720
|
local_port = int(os.getenv("TB_LOCAL_PORT", 80))
|
|
721
721
|
local_host = f"http://localhost:{local_port}"
|
|
@@ -725,16 +725,16 @@ class TinyB:
|
|
|
725
725
|
local_workspaces = [x for x in data["workspaces"] if not x["name"].startswith("Tinybird_Local_")]
|
|
726
726
|
return {**data, "workspaces": local_workspaces}
|
|
727
727
|
|
|
728
|
-
async def user_workspaces_and_branches(self):
|
|
729
|
-
return await self._req("/
|
|
728
|
+
async def user_workspaces_and_branches(self, version: str = "v0"):
|
|
729
|
+
return await self._req(f"/{version}/user/workspaces/?with_environments=true")
|
|
730
730
|
|
|
731
|
-
async def user_workspaces_with_organization(self):
|
|
731
|
+
async def user_workspaces_with_organization(self, version: str = "v0"):
|
|
732
732
|
return await self._req(
|
|
733
|
-
"/
|
|
733
|
+
f"/{version}/user/workspaces/?with_environments=false&with_organization=true&with_members_and_owner=false"
|
|
734
734
|
)
|
|
735
735
|
|
|
736
|
-
async def user_workspace_branches(self):
|
|
737
|
-
return await self._req("/
|
|
736
|
+
async def user_workspace_branches(self, version: str = "v0"):
|
|
737
|
+
return await self._req(f"/{version}/user/workspaces/?with_environments=true&only_environments=true")
|
|
738
738
|
|
|
739
739
|
async def branches(self):
|
|
740
740
|
return await self._req("/v0/environments")
|
|
@@ -747,8 +747,9 @@ class TinyB:
|
|
|
747
747
|
name: str,
|
|
748
748
|
template: Optional[str],
|
|
749
749
|
assign_to_organization_id: Optional[str] = None,
|
|
750
|
+
version: str = "v0",
|
|
750
751
|
):
|
|
751
|
-
url = f"/
|
|
752
|
+
url = f"/{version}/workspaces?name={name}"
|
|
752
753
|
if template:
|
|
753
754
|
url += f"&starter_kit={template}"
|
|
754
755
|
if assign_to_organization_id:
|
|
@@ -884,8 +885,8 @@ class TinyB:
|
|
|
884
885
|
params = {"with_token": "true" if with_token else "false"}
|
|
885
886
|
return await self._req(f"/v0/workspaces/{workspace_id}?{urlencode(params)}")
|
|
886
887
|
|
|
887
|
-
async def workspace_info(self) -> Dict[str, Any]:
|
|
888
|
-
return await self._req("/
|
|
888
|
+
async def workspace_info(self, version: str = "v0") -> Dict[str, Any]:
|
|
889
|
+
return await self._req(f"/{version}/workspace")
|
|
889
890
|
|
|
890
891
|
async def organization(self, organization_id: str):
|
|
891
892
|
return await self._req(f"/v0/organizations/{organization_id}")
|
|
@@ -906,6 +907,21 @@ class TinyB:
|
|
|
906
907
|
data=json.dumps({"workspace_ids": ",".join(workspace_ids)}),
|
|
907
908
|
)
|
|
908
909
|
|
|
910
|
+
async def infra_create(self, organization_id: str, name: str, host: str) -> Dict[str, Any]:
|
|
911
|
+
params = {
|
|
912
|
+
"organization_id": organization_id,
|
|
913
|
+
"name": name,
|
|
914
|
+
"host": host,
|
|
915
|
+
}
|
|
916
|
+
return await self._req(f"/v1/infra?{urlencode(params)}", method="POST")
|
|
917
|
+
|
|
918
|
+
async def infra_list(self, organization_id: str) -> List[Dict[str, Any]]:
|
|
919
|
+
data = await self._req(f"/v1/infra?organization_id={organization_id}")
|
|
920
|
+
return data.get("infras", [])
|
|
921
|
+
|
|
922
|
+
async def infra_delete(self, infra_id: str, organization_id: str) -> Dict[str, Any]:
|
|
923
|
+
return await self._req(f"/v1/infra/{infra_id}?organization_id={organization_id}", method="DELETE")
|
|
924
|
+
|
|
909
925
|
async def wait_for_job(
|
|
910
926
|
self,
|
|
911
927
|
job_id: str,
|
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.dev98'
|
|
8
|
+
__revision__ = 'f7b04b3'
|
tinybird/tb/modules/cli.py
CHANGED
|
@@ -121,7 +121,7 @@ async def cli(
|
|
|
121
121
|
# If they have passed a token or host as parameter and it's different that record in .tinyb, refresh the workspace id
|
|
122
122
|
if token or host:
|
|
123
123
|
try:
|
|
124
|
-
workspace = await client.workspace_info()
|
|
124
|
+
workspace = await client.workspace_info(version="v1")
|
|
125
125
|
config["id"] = workspace.get("id", "")
|
|
126
126
|
config["name"] = workspace.get("name", "")
|
|
127
127
|
# If we can not get this info, we continue with the id on the file
|
|
@@ -236,7 +236,7 @@ async def diff(
|
|
|
236
236
|
else:
|
|
237
237
|
config = CLIConfig.get_project_config()
|
|
238
238
|
|
|
239
|
-
response = await client.user_workspaces_and_branches()
|
|
239
|
+
response = await client.user_workspaces_and_branches(version="v1")
|
|
240
240
|
ws_client = None
|
|
241
241
|
for workspace in response["workspaces"]:
|
|
242
242
|
if config["id"] == workspace["id"]:
|
tinybird/tb/modules/common.py
CHANGED
|
@@ -198,7 +198,7 @@ def generate_datafile(
|
|
|
198
198
|
|
|
199
199
|
async def get_current_workspace(config: CLIConfig) -> Optional[Dict[str, Any]]:
|
|
200
200
|
client = config.get_client()
|
|
201
|
-
workspaces: List[Dict[str, Any]] = (await client.user_workspaces_and_branches()).get("workspaces", [])
|
|
201
|
+
workspaces: List[Dict[str, Any]] = (await client.user_workspaces_and_branches(version="v1")).get("workspaces", [])
|
|
202
202
|
return _get_current_workspace_common(workspaces, config["id"])
|
|
203
203
|
|
|
204
204
|
|
|
@@ -213,7 +213,7 @@ def _get_current_workspace_common(
|
|
|
213
213
|
|
|
214
214
|
|
|
215
215
|
async def get_current_environment(client, config):
|
|
216
|
-
workspaces: List[Dict[str, Any]] = (await client.user_workspaces_and_branches()).get("workspaces", [])
|
|
216
|
+
workspaces: List[Dict[str, Any]] = (await client.user_workspaces_and_branches(version="v1")).get("workspaces", [])
|
|
217
217
|
return next((workspace for workspace in workspaces if workspace["id"] == config["id"]), None)
|
|
218
218
|
|
|
219
219
|
|
|
@@ -700,7 +700,9 @@ async def create_workspace_non_interactive(
|
|
|
700
700
|
user_client: TinyB = deepcopy(client)
|
|
701
701
|
user_client.token = user_token
|
|
702
702
|
|
|
703
|
-
created_workspace = await user_client.create_workspace(
|
|
703
|
+
created_workspace = await user_client.create_workspace(
|
|
704
|
+
workspace_name, starterkit, organization_id, version="v1"
|
|
705
|
+
)
|
|
704
706
|
if organization_id and organization_name:
|
|
705
707
|
click.echo(
|
|
706
708
|
FeedbackManager.success_workspace_created_with_organization(
|
|
@@ -1210,7 +1212,7 @@ def _get_setting_value(connection, setting, sensitive_settings):
|
|
|
1210
1212
|
|
|
1211
1213
|
async def switch_workspace(config: CLIConfig, workspace_name_or_id: str) -> None:
|
|
1212
1214
|
try:
|
|
1213
|
-
response = await config.get_client().user_workspaces()
|
|
1215
|
+
response = await config.get_client().user_workspaces(version="v1")
|
|
1214
1216
|
workspaces = response["workspaces"]
|
|
1215
1217
|
|
|
1216
1218
|
workspace = next(
|
|
@@ -1436,7 +1438,7 @@ async def try_update_config_with_remote(
|
|
|
1436
1438
|
return True
|
|
1437
1439
|
|
|
1438
1440
|
try:
|
|
1439
|
-
response = await config.get_client().workspace_info()
|
|
1441
|
+
response = await config.get_client().workspace_info(version="v1")
|
|
1440
1442
|
except AuthException:
|
|
1441
1443
|
if raise_on_errors:
|
|
1442
1444
|
raise CLIAuthException(FeedbackManager.error_invalid_token_for_host(host=config.get_host()))
|
|
@@ -1606,7 +1608,7 @@ async def wait_job_no_ui(
|
|
|
1606
1608
|
|
|
1607
1609
|
|
|
1608
1610
|
async def get_current_main_workspace(config: CLIConfig) -> Optional[Dict[str, Any]]:
|
|
1609
|
-
current_workspace = await config.get_client().user_workspaces_and_branches()
|
|
1611
|
+
current_workspace = await config.get_client().user_workspaces_and_branches(version="v1")
|
|
1610
1612
|
return _get_current_main_workspace_common(current_workspace, config.get("id", current_workspace["id"]))
|
|
1611
1613
|
|
|
1612
1614
|
|
|
@@ -1988,7 +1990,7 @@ async def get_organizations_by_user(config: CLIConfig, user_token: str) -> List[
|
|
|
1988
1990
|
|
|
1989
1991
|
try:
|
|
1990
1992
|
user_client = config.get_client(token=user_token)
|
|
1991
|
-
user_workspaces = await user_client.user_workspaces_with_organization()
|
|
1993
|
+
user_workspaces = await user_client.user_workspaces_with_organization(version="v1")
|
|
1992
1994
|
admin_org_id = user_workspaces.get("organization_id")
|
|
1993
1995
|
seen_org_ids = set()
|
|
1994
1996
|
|
|
@@ -2105,7 +2107,7 @@ async def create_organization_and_add_workspaces(
|
|
|
2105
2107
|
raise CLIWorkspaceException(FeedbackManager.error_organization_creation(error=str(e)))
|
|
2106
2108
|
|
|
2107
2109
|
# Add existing orphan workspaces to the organization - this is only needed for backwards compatibility
|
|
2108
|
-
user_workspaces = await client.user_workspaces_with_organization()
|
|
2110
|
+
user_workspaces = await client.user_workspaces_with_organization(version="v1")
|
|
2109
2111
|
workspaces_to_migrate = []
|
|
2110
2112
|
for workspace in user_workspaces["workspaces"]:
|
|
2111
2113
|
if workspace.get("organization") is None and workspace.get("role") == "admin":
|
tinybird/tb/modules/config.py
CHANGED
|
@@ -279,7 +279,7 @@ class CLIConfig:
|
|
|
279
279
|
|
|
280
280
|
client: tbc.TinyB = self.get_client(token=self.get_user_token(), host=host)
|
|
281
281
|
|
|
282
|
-
info: Dict[str, Any] = await client.user_workspaces_and_branches()
|
|
282
|
+
info: Dict[str, Any] = await client.user_workspaces_and_branches(version="v1")
|
|
283
283
|
workspaces: List[Dict[str, Any]] = info["workspaces"]
|
|
284
284
|
|
|
285
285
|
result: Optional[str] = next(
|
|
@@ -1015,7 +1015,7 @@ async def process_file(
|
|
|
1015
1015
|
period: int = DEFAULT_CRON_PERIOD
|
|
1016
1016
|
|
|
1017
1017
|
if current_ws is not None:
|
|
1018
|
-
workspaces = (await tb_client.user_workspaces()).get("workspaces", [])
|
|
1018
|
+
workspaces = (await tb_client.user_workspaces(version="v1")).get("workspaces", [])
|
|
1019
1019
|
workspace_rate_limits: Dict[str, Dict[str, int]] = next(
|
|
1020
1020
|
(w.get("rate_limits", {}) for w in workspaces if w["id"] == current_ws["id"]), {}
|
|
1021
1021
|
)
|
|
@@ -346,9 +346,9 @@ async def share_and_unshare_datasource(
|
|
|
346
346
|
if current_ws:
|
|
347
347
|
# Force to get all the workspaces the user can access
|
|
348
348
|
workspace = current_ws
|
|
349
|
-
workspaces = (await client.user_workspaces()).get("workspaces", [])
|
|
349
|
+
workspaces = (await client.user_workspaces(version="v1")).get("workspaces", [])
|
|
350
350
|
else:
|
|
351
|
-
workspace = await client.user_workspace_branches()
|
|
351
|
+
workspace = await client.user_workspace_branches(version="v1")
|
|
352
352
|
workspaces = workspace.get("workspaces", [])
|
|
353
353
|
|
|
354
354
|
if workspace.get("is_branch", False):
|
|
@@ -166,10 +166,10 @@ async def new_pipe(
|
|
|
166
166
|
|
|
167
167
|
async def get_token_from_main_branch(branch_tb_client: TinyB) -> Optional[str]:
|
|
168
168
|
token_from_main_branch = None
|
|
169
|
-
current_workspace = await branch_tb_client.workspace_info()
|
|
169
|
+
current_workspace = await branch_tb_client.workspace_info(version="v1")
|
|
170
170
|
# current workspace is a branch
|
|
171
171
|
if current_workspace.get("main"):
|
|
172
|
-
response = await branch_tb_client.user_workspaces()
|
|
172
|
+
response = await branch_tb_client.user_workspaces(version="v1")
|
|
173
173
|
workspaces = response["workspaces"]
|
|
174
174
|
prod_workspace = next(
|
|
175
175
|
(workspace for workspace in workspaces if workspace["id"] == current_workspace["main"]), None
|
|
@@ -1172,7 +1172,7 @@ async def process_file(
|
|
|
1172
1172
|
period: int = DEFAULT_CRON_PERIOD
|
|
1173
1173
|
|
|
1174
1174
|
if current_ws is not None:
|
|
1175
|
-
workspaces = (await tb_client.user_workspaces()).get("workspaces", [])
|
|
1175
|
+
workspaces = (await tb_client.user_workspaces(version="v1")).get("workspaces", [])
|
|
1176
1176
|
workspace_rate_limits: Dict[str, Dict[str, int]] = next(
|
|
1177
1177
|
(w.get("rate_limits", {}) for w in workspaces if w["id"] == current_ws["id"]), {}
|
|
1178
1178
|
)
|
tinybird/tb/modules/infra.py
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import uuid
|
|
1
3
|
from pathlib import Path
|
|
2
4
|
from typing import Optional
|
|
3
5
|
|
|
4
6
|
import click
|
|
5
7
|
from click import Context
|
|
6
8
|
|
|
7
|
-
from tinybird.
|
|
9
|
+
from tinybird.client import TinyB
|
|
10
|
+
from tinybird.syncasync import async_to_sync
|
|
11
|
+
from tinybird.tb.modules.cli import CLIException, cli
|
|
12
|
+
from tinybird.tb.modules.common import coro, echo_safe_humanfriendly_tables_format_smart_table
|
|
13
|
+
from tinybird.tb.modules.config import CLIConfig
|
|
14
|
+
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
8
15
|
|
|
9
16
|
from .common import CONTEXT_SETTINGS
|
|
10
17
|
|
|
@@ -51,13 +58,12 @@ spec:
|
|
|
51
58
|
name: tinybird
|
|
52
59
|
---
|
|
53
60
|
apiVersion: apps/v1
|
|
54
|
-
kind:
|
|
61
|
+
kind: StatefulSet
|
|
55
62
|
metadata:
|
|
56
63
|
name: tinybird
|
|
57
64
|
namespace: %(namespace)s
|
|
58
|
-
labels:
|
|
59
|
-
name: tinybird
|
|
60
65
|
spec:
|
|
66
|
+
serviceName: "tinybird"
|
|
61
67
|
replicas: 1
|
|
62
68
|
selector:
|
|
63
69
|
matchLabels:
|
|
@@ -76,6 +82,21 @@ spec:
|
|
|
76
82
|
- name: http
|
|
77
83
|
containerPort: 7181
|
|
78
84
|
protocol: TCP
|
|
85
|
+
env:
|
|
86
|
+
- name: TB_INFRA_TOKEN
|
|
87
|
+
value: "%(infra_token)s"
|
|
88
|
+
volumeMounts:
|
|
89
|
+
- name: clickhouse-data
|
|
90
|
+
mountPath: /var/lib/clickhouse
|
|
91
|
+
volumeClaimTemplates:
|
|
92
|
+
- metadata:
|
|
93
|
+
name: clickhouse-data
|
|
94
|
+
spec:
|
|
95
|
+
accessModes: [ "ReadWriteOnce" ]
|
|
96
|
+
resources:
|
|
97
|
+
requests:
|
|
98
|
+
storage: 100Gi
|
|
99
|
+
storageClassName: %(storage_class)s
|
|
79
100
|
"""
|
|
80
101
|
|
|
81
102
|
TERRAFORM_FIRST_TEMPLATE = """
|
|
@@ -161,13 +182,8 @@ def infra(ctx: Context) -> None:
|
|
|
161
182
|
"""Infra commands."""
|
|
162
183
|
|
|
163
184
|
|
|
164
|
-
@infra.command(name="ls", hidden=True)
|
|
165
|
-
@click.pass_context
|
|
166
|
-
async def infra_ls(ctx: Context) -> None:
|
|
167
|
-
"""List infra"""
|
|
168
|
-
|
|
169
|
-
|
|
170
185
|
@infra.command(name="init")
|
|
186
|
+
@click.option("--name", type=str, help="Name for identifying the self-managed infrastructure in Tinybird")
|
|
171
187
|
@click.option("--provider", default="aws", type=str, help="Infrastructure provider (aws, gcp, azure)")
|
|
172
188
|
@click.option("--region", type=str, help="AWS region (for AWS provider)")
|
|
173
189
|
@click.option("--domain", type=str, help="Route53 domain name (for AWS provider)")
|
|
@@ -186,11 +202,13 @@ async def infra_ls(ctx: Context) -> None:
|
|
|
186
202
|
@click.pass_context
|
|
187
203
|
def infra_init(
|
|
188
204
|
ctx: Context,
|
|
205
|
+
name: str,
|
|
189
206
|
provider: str,
|
|
190
207
|
region: Optional[str] = None,
|
|
191
208
|
domain: Optional[str] = None,
|
|
192
209
|
namespace: Optional[str] = None,
|
|
193
210
|
dns_record: Optional[str] = None,
|
|
211
|
+
storage_class: Optional[str] = None,
|
|
194
212
|
auto_apply_terraform: bool = False,
|
|
195
213
|
auto_apply_dns: bool = False,
|
|
196
214
|
auto_apply_kubectl: bool = False,
|
|
@@ -209,11 +227,69 @@ def infra_init(
|
|
|
209
227
|
infra_dir.mkdir(exist_ok=True)
|
|
210
228
|
yaml_path = infra_dir / "k8s.yaml"
|
|
211
229
|
tf_path = infra_dir / "main.tf"
|
|
230
|
+
config_path = infra_dir / "config.json"
|
|
231
|
+
|
|
232
|
+
# Load existing configuration if available
|
|
233
|
+
config = {}
|
|
234
|
+
if config_path.exists():
|
|
235
|
+
try:
|
|
236
|
+
with open(config_path, "r") as f:
|
|
237
|
+
config = json.load(f)
|
|
238
|
+
click.echo("Loaded existing configuration from config.json")
|
|
239
|
+
except json.JSONDecodeError:
|
|
240
|
+
click.echo("Warning: Could not parse existing config.json, will create a new one")
|
|
241
|
+
|
|
242
|
+
# Generate a random ID for default values
|
|
243
|
+
random_id = str(uuid.uuid4())[:8]
|
|
244
|
+
|
|
245
|
+
# Get or prompt for configuration values
|
|
246
|
+
name = name or click.prompt("Enter name", type=str)
|
|
247
|
+
region = region or config.get("region") or click.prompt("Enter aws region", default="us-east-1", type=str)
|
|
248
|
+
domain = domain or config.get("domain") or click.prompt("Enter route 53 domain name", type=str)
|
|
249
|
+
namespace = (
|
|
250
|
+
namespace
|
|
251
|
+
or config.get("namespace")
|
|
252
|
+
or click.prompt("Enter namespace name", default=f"tinybird-{random_id}", type=str)
|
|
253
|
+
)
|
|
254
|
+
dns_record = (
|
|
255
|
+
dns_record
|
|
256
|
+
or config.get("dns_record")
|
|
257
|
+
or click.prompt("Enter DNS record name (without domain)", default=f"tinybird-{random_id}", type=str)
|
|
258
|
+
)
|
|
259
|
+
storage_class = config.get("storage_class") or click.prompt(
|
|
260
|
+
"Enter storage class", default="gp3-encrypted", type=str
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
# Save configuration
|
|
264
|
+
config = {
|
|
265
|
+
"provider": provider,
|
|
266
|
+
"region": region,
|
|
267
|
+
"domain": domain,
|
|
268
|
+
"namespace": namespace,
|
|
269
|
+
"dns_record": dns_record,
|
|
270
|
+
"storage_class": storage_class,
|
|
271
|
+
}
|
|
212
272
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
273
|
+
with open(config_path, "w") as f:
|
|
274
|
+
json.dump(config, f, indent=2)
|
|
275
|
+
|
|
276
|
+
click.echo(f"Configuration saved to {config_path}")
|
|
277
|
+
|
|
278
|
+
client: TinyB = ctx.obj["client"]
|
|
279
|
+
cli_config = CLIConfig.get_project_config()
|
|
280
|
+
user_client = cli_config.get_client(token=cli_config.get_user_token() or "")
|
|
281
|
+
user_workspaces = async_to_sync(user_client.user_workspaces_with_organization)()
|
|
282
|
+
admin_org_id = user_workspaces.get("organization_id")
|
|
283
|
+
infras = async_to_sync(client.infra_list)(organization_id=admin_org_id)
|
|
284
|
+
infra = next((infra for infra in infras if infra["name"] == name), None)
|
|
285
|
+
if not infra:
|
|
286
|
+
click.echo(FeedbackManager.highlight(message=f"\n» Creating infrastructure '{name}' in Tinybird..."))
|
|
287
|
+
host = f"https://{dns_record}.{domain}"
|
|
288
|
+
infra = async_to_sync(client.infra_create)(organization_id=admin_org_id, name=name, host=host)
|
|
289
|
+
|
|
290
|
+
infra_token = infra["token"]
|
|
216
291
|
|
|
292
|
+
# Write the Terraform template
|
|
217
293
|
terraform_content = TERRAFORM_FIRST_TEMPLATE % {"aws_region": region, "domain": domain}
|
|
218
294
|
|
|
219
295
|
with open(tf_path, "w") as f:
|
|
@@ -222,9 +298,7 @@ def infra_init(
|
|
|
222
298
|
click.echo(f"Creating Terraform configuration in {tf_path}")
|
|
223
299
|
|
|
224
300
|
# Apply Terraform configuration if user confirms
|
|
225
|
-
if not skip_terraform
|
|
226
|
-
auto_apply_terraform or click.confirm("Would you like to apply the Terraform configuration now?")
|
|
227
|
-
):
|
|
301
|
+
if not skip_terraform:
|
|
228
302
|
import subprocess
|
|
229
303
|
|
|
230
304
|
# Initialize Terraform
|
|
@@ -236,272 +310,384 @@ def infra_init(
|
|
|
236
310
|
click.echo(init_result.stderr)
|
|
237
311
|
return
|
|
238
312
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
click.echo("\nApplying Terraform configuration...\n")
|
|
243
|
-
apply_result = subprocess.run(
|
|
244
|
-
["terraform", "-chdir=infra", "apply", "-auto-approve"], capture_output=True, text=True
|
|
245
|
-
)
|
|
313
|
+
# Run terraform plan first
|
|
314
|
+
click.echo("\nRunning Terraform plan...\n")
|
|
315
|
+
plan_result = subprocess.run(["terraform", "-chdir=infra", "plan"], capture_output=True, text=True)
|
|
246
316
|
|
|
247
|
-
if
|
|
248
|
-
click.echo("Terraform
|
|
249
|
-
click.echo(
|
|
317
|
+
if plan_result.returncode != 0:
|
|
318
|
+
click.echo("Terraform plan failed:")
|
|
319
|
+
click.echo(plan_result.stderr)
|
|
250
320
|
return
|
|
251
321
|
|
|
252
|
-
click.echo(
|
|
322
|
+
click.echo(plan_result.stdout)
|
|
253
323
|
|
|
254
|
-
#
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
324
|
+
# Apply Terraform configuration if user confirms
|
|
325
|
+
if auto_apply_terraform or click.confirm("Would you like to apply the Terraform configuration now?"):
|
|
326
|
+
click.echo("\nApplying Terraform configuration...\n")
|
|
327
|
+
apply_result = subprocess.run(
|
|
328
|
+
["terraform", "-chdir=infra", "apply", "-auto-approve"], capture_output=True, text=True
|
|
329
|
+
)
|
|
258
330
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
new_content = K8S_YML % {"namespace": namespace, "cert_arn": cert_arn}
|
|
264
|
-
|
|
265
|
-
with open(yaml_path, "w") as f:
|
|
266
|
-
f.write(new_content.lstrip())
|
|
267
|
-
|
|
268
|
-
click.echo(f"Created Kubernetes configuration with certificate ARN in {yaml_path}")
|
|
269
|
-
|
|
270
|
-
# Prompt to apply the k8s configuration
|
|
271
|
-
if not skip_kubectl and (
|
|
272
|
-
auto_apply_kubectl or click.confirm("Would you like to apply the Kubernetes configuration now?")
|
|
273
|
-
):
|
|
274
|
-
import subprocess
|
|
275
|
-
|
|
276
|
-
# Get current kubectl context
|
|
277
|
-
current_context_result = subprocess.run(
|
|
278
|
-
["kubectl", "config", "current-context"], capture_output=True, text=True
|
|
279
|
-
)
|
|
280
|
-
|
|
281
|
-
current_context = (
|
|
282
|
-
current_context_result.stdout.strip() if current_context_result.returncode == 0 else "unknown"
|
|
283
|
-
)
|
|
284
|
-
|
|
285
|
-
# Get available contexts
|
|
286
|
-
contexts_result = subprocess.run(
|
|
287
|
-
["kubectl", "config", "get-contexts", "-o", "name"], capture_output=True, text=True
|
|
288
|
-
)
|
|
289
|
-
|
|
290
|
-
if contexts_result.returncode != 0:
|
|
291
|
-
click.echo("Failed to get kubectl contexts:")
|
|
292
|
-
click.echo(contexts_result.stderr)
|
|
293
|
-
return
|
|
294
|
-
|
|
295
|
-
available_contexts = [
|
|
296
|
-
context.strip() for context in contexts_result.stdout.splitlines() if context.strip()
|
|
297
|
-
]
|
|
298
|
-
|
|
299
|
-
if not available_contexts:
|
|
300
|
-
click.echo("No kubectl contexts found. Please configure kubectl first.")
|
|
301
|
-
return
|
|
302
|
-
|
|
303
|
-
# Prompt user to select a context
|
|
304
|
-
if len(available_contexts) == 1:
|
|
305
|
-
selected_context = available_contexts[0]
|
|
306
|
-
click.echo(f"Using the only available kubectl context: {selected_context}")
|
|
307
|
-
else:
|
|
308
|
-
click.echo("\nAvailable kubectl contexts:")
|
|
309
|
-
for i, context in enumerate(available_contexts):
|
|
310
|
-
marker = " (current)" if context == current_context else ""
|
|
311
|
-
click.echo(f" {i + 1}. {context}{marker}")
|
|
312
|
-
|
|
313
|
-
click.echo("")
|
|
314
|
-
default_index = (
|
|
315
|
-
available_contexts.index(current_context) + 1 if current_context in available_contexts else 1
|
|
316
|
-
)
|
|
331
|
+
if apply_result.returncode != 0:
|
|
332
|
+
click.echo("Terraform apply failed:")
|
|
333
|
+
click.echo(apply_result.stderr)
|
|
334
|
+
return
|
|
317
335
|
|
|
318
|
-
|
|
319
|
-
"Select kubectl context number to apply configuration",
|
|
320
|
-
type=click.IntRange(1, len(available_contexts)),
|
|
321
|
-
default=default_index,
|
|
322
|
-
)
|
|
336
|
+
click.echo(apply_result.stdout)
|
|
323
337
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
click.echo(f"Applying Kubernetes configuration to context '{selected_context}'...")
|
|
329
|
-
apply_result = subprocess.run(
|
|
330
|
-
["kubectl", "--context", selected_context, "apply", "-f", str(yaml_path)],
|
|
331
|
-
capture_output=True,
|
|
332
|
-
text=True,
|
|
333
|
-
)
|
|
334
|
-
|
|
335
|
-
if apply_result.returncode != 0:
|
|
336
|
-
click.echo("Failed to apply Kubernetes configuration:")
|
|
337
|
-
click.echo(apply_result.stderr)
|
|
338
|
-
else:
|
|
339
|
-
click.echo("Kubernetes configuration applied successfully:")
|
|
340
|
-
click.echo(apply_result.stdout)
|
|
341
|
-
|
|
342
|
-
# Get the namespace from the applied configuration
|
|
343
|
-
namespace = None
|
|
344
|
-
with open(yaml_path, "r") as f:
|
|
345
|
-
for line in f:
|
|
346
|
-
if "namespace:" in line and not namespace:
|
|
347
|
-
namespace = line.split("namespace:")[1].strip()
|
|
348
|
-
break
|
|
349
|
-
|
|
350
|
-
if not namespace:
|
|
351
|
-
namespace = "tinybird" # Default namespace
|
|
352
|
-
|
|
353
|
-
click.echo(f"\nWaiting for load balancer to be provisioned in namespace '{namespace}'...")
|
|
354
|
-
|
|
355
|
-
# Wait for the load balancer to get an external IP
|
|
356
|
-
max_attempts = 30
|
|
357
|
-
attempt = 0
|
|
358
|
-
external_ip = None
|
|
359
|
-
|
|
360
|
-
while attempt < max_attempts and not external_ip:
|
|
361
|
-
attempt += 1
|
|
362
|
-
|
|
363
|
-
# Get the service details
|
|
364
|
-
get_service_result = subprocess.run(
|
|
365
|
-
[
|
|
366
|
-
"kubectl",
|
|
367
|
-
"--context",
|
|
368
|
-
selected_context,
|
|
369
|
-
"-n",
|
|
370
|
-
namespace,
|
|
371
|
-
"get",
|
|
372
|
-
"service",
|
|
373
|
-
"tinybird",
|
|
374
|
-
"-o",
|
|
375
|
-
"jsonpath='{.status.loadBalancer.ingress[0].hostname}'",
|
|
376
|
-
],
|
|
377
|
-
capture_output=True,
|
|
378
|
-
text=True,
|
|
379
|
-
)
|
|
338
|
+
# Get the certificate ARN output
|
|
339
|
+
output_result = subprocess.run(
|
|
340
|
+
["terraform", "-chdir=infra", "output", "certificate_arn"], capture_output=True, text=True
|
|
341
|
+
)
|
|
380
342
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
if potential_ip and potential_ip != "":
|
|
384
|
-
external_ip = potential_ip
|
|
385
|
-
break
|
|
343
|
+
if output_result.returncode == 0:
|
|
344
|
+
cert_arn = output_result.stdout.strip().replace('"', "")
|
|
386
345
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
346
|
+
new_content = K8S_YML % {
|
|
347
|
+
"namespace": namespace,
|
|
348
|
+
"cert_arn": cert_arn,
|
|
349
|
+
"storage_class": storage_class,
|
|
350
|
+
"infra_token": infra_token,
|
|
351
|
+
}
|
|
391
352
|
|
|
392
|
-
|
|
353
|
+
with open(yaml_path, "w") as f:
|
|
354
|
+
f.write(new_content.lstrip())
|
|
393
355
|
|
|
394
|
-
|
|
395
|
-
click.echo("\n✅ Load balancer provisioned successfully!")
|
|
356
|
+
click.echo(f"Created Kubernetes configuration with certificate ARN in {yaml_path}")
|
|
396
357
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
358
|
+
# Prompt to apply the k8s configuration
|
|
359
|
+
if not skip_kubectl and (
|
|
360
|
+
auto_apply_kubectl or click.confirm("Would you like to apply the Kubernetes configuration now?")
|
|
361
|
+
):
|
|
362
|
+
import subprocess
|
|
400
363
|
|
|
401
|
-
|
|
402
|
-
|
|
364
|
+
# Get current kubectl context
|
|
365
|
+
current_context_result = subprocess.run(
|
|
366
|
+
["kubectl", "config", "current-context"], capture_output=True, text=True
|
|
367
|
+
)
|
|
403
368
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
dns_record = dns_record or click.prompt(
|
|
408
|
-
"Enter DNS record name (without domain)", default="tinybird", type=str
|
|
409
|
-
)
|
|
369
|
+
current_context = (
|
|
370
|
+
current_context_result.stdout.strip() if current_context_result.returncode == 0 else "unknown"
|
|
371
|
+
)
|
|
410
372
|
|
|
411
|
-
|
|
412
|
-
|
|
373
|
+
# Get available contexts
|
|
374
|
+
contexts_result = subprocess.run(
|
|
375
|
+
["kubectl", "config", "get-contexts", "-o", "name"], capture_output=True, text=True
|
|
376
|
+
)
|
|
413
377
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
378
|
+
if contexts_result.returncode != 0:
|
|
379
|
+
click.echo("Failed to get kubectl contexts:")
|
|
380
|
+
click.echo(contexts_result.stderr)
|
|
381
|
+
return
|
|
382
|
+
|
|
383
|
+
available_contexts = [
|
|
384
|
+
context.strip() for context in contexts_result.stdout.splitlines() if context.strip()
|
|
385
|
+
]
|
|
386
|
+
|
|
387
|
+
if not available_contexts:
|
|
388
|
+
click.echo("No kubectl contexts found. Please configure kubectl first.")
|
|
389
|
+
return
|
|
390
|
+
|
|
391
|
+
# Prompt user to select a context
|
|
392
|
+
if len(available_contexts) == 1:
|
|
393
|
+
selected_context = available_contexts[0]
|
|
394
|
+
click.echo(f"Using the only available kubectl context: {selected_context}")
|
|
395
|
+
else:
|
|
396
|
+
click.echo("\nAvailable kubectl contexts:")
|
|
397
|
+
for i, context in enumerate(available_contexts):
|
|
398
|
+
marker = " (current)" if context == current_context else ""
|
|
399
|
+
click.echo(f" {i + 1}. {context}{marker}")
|
|
400
|
+
|
|
401
|
+
click.echo("")
|
|
402
|
+
default_index = (
|
|
403
|
+
available_contexts.index(current_context) + 1
|
|
404
|
+
if current_context in available_contexts
|
|
405
|
+
else 1
|
|
406
|
+
)
|
|
419
407
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
408
|
+
selected_index = click.prompt(
|
|
409
|
+
"Select kubectl context number to apply configuration",
|
|
410
|
+
type=click.IntRange(1, len(available_contexts)),
|
|
411
|
+
default=default_index,
|
|
412
|
+
)
|
|
423
413
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
# Update the existing Route 53 record
|
|
427
|
-
updated_tf = tf_content.replace(
|
|
428
|
-
'records = ["LOAD_BALANCER_DNS_PLACEHOLDER"]', f'records = ["{external_ip}"]'
|
|
429
|
-
)
|
|
414
|
+
selected_context = available_contexts[selected_index - 1]
|
|
415
|
+
click.echo(f"Selected context: {selected_context}")
|
|
430
416
|
|
|
431
|
-
|
|
432
|
-
|
|
417
|
+
# Apply the configuration to the selected context
|
|
418
|
+
click.echo(f"Applying Kubernetes configuration to context '{selected_context}'...")
|
|
419
|
+
apply_result = subprocess.run(
|
|
420
|
+
["kubectl", "--context", selected_context, "apply", "-f", str(yaml_path)],
|
|
421
|
+
capture_output=True,
|
|
422
|
+
text=True,
|
|
423
|
+
)
|
|
433
424
|
|
|
434
|
-
|
|
435
|
-
|
|
425
|
+
if apply_result.returncode != 0:
|
|
426
|
+
click.echo("Failed to apply Kubernetes configuration:")
|
|
427
|
+
click.echo(apply_result.stderr)
|
|
428
|
+
else:
|
|
429
|
+
click.echo("Kubernetes configuration applied successfully:")
|
|
430
|
+
click.echo(apply_result.stdout)
|
|
431
|
+
|
|
432
|
+
# Get the namespace from the applied configuration
|
|
433
|
+
namespace = None
|
|
434
|
+
with open(yaml_path, "r") as f:
|
|
435
|
+
for line in f:
|
|
436
|
+
if "namespace:" in line and not namespace:
|
|
437
|
+
namespace = line.split("namespace:")[1].strip()
|
|
438
|
+
break
|
|
439
|
+
|
|
440
|
+
if not namespace:
|
|
441
|
+
namespace = "tinybird" # Default namespace
|
|
442
|
+
|
|
443
|
+
click.echo(f"\nWaiting for load balancer to be provisioned in namespace '{namespace}'...")
|
|
444
|
+
|
|
445
|
+
# Wait for the load balancer to get an external IP
|
|
446
|
+
max_attempts = 30
|
|
447
|
+
attempt = 0
|
|
448
|
+
external_ip = None
|
|
449
|
+
|
|
450
|
+
while attempt < max_attempts and not external_ip:
|
|
451
|
+
attempt += 1
|
|
452
|
+
|
|
453
|
+
# Get the service details
|
|
454
|
+
get_service_result = subprocess.run(
|
|
455
|
+
[
|
|
456
|
+
"kubectl",
|
|
457
|
+
"--context",
|
|
458
|
+
selected_context,
|
|
459
|
+
"-n",
|
|
460
|
+
namespace,
|
|
461
|
+
"get",
|
|
462
|
+
"service",
|
|
463
|
+
"tinybird",
|
|
464
|
+
"-o",
|
|
465
|
+
"jsonpath='{.status.loadBalancer.ingress[0].hostname}'",
|
|
466
|
+
],
|
|
467
|
+
capture_output=True,
|
|
468
|
+
text=True,
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
if get_service_result.returncode == 0:
|
|
472
|
+
potential_ip = get_service_result.stdout.strip().replace("'", "")
|
|
473
|
+
if potential_ip and potential_ip != "":
|
|
474
|
+
external_ip = potential_ip
|
|
475
|
+
break
|
|
476
|
+
|
|
477
|
+
click.echo(
|
|
478
|
+
f"Attempt {attempt}/{max_attempts}: Load balancer not ready yet, waiting 10 seconds..."
|
|
479
|
+
)
|
|
480
|
+
import time
|
|
481
|
+
|
|
482
|
+
time.sleep(10)
|
|
483
|
+
|
|
484
|
+
if external_ip:
|
|
485
|
+
click.echo("\nLoad balancer provisioned successfully.")
|
|
486
|
+
|
|
487
|
+
# Update the Terraform configuration with the load balancer DNS
|
|
488
|
+
if not skip_dns and domain and tf_path.exists():
|
|
489
|
+
click.echo("\nUpdating Terraform configuration with load balancer DNS...")
|
|
490
|
+
|
|
491
|
+
with open(tf_path, "r") as f:
|
|
492
|
+
tf_content = f.read()
|
|
493
|
+
|
|
494
|
+
# Check if the Route 53 record already exists in the file
|
|
495
|
+
if 'resource "aws_route53_record" "tinybird"' not in tf_content:
|
|
496
|
+
# Create the full DNS name
|
|
497
|
+
full_dns_name = f"{dns_record}.{domain}"
|
|
498
|
+
|
|
499
|
+
# Use in the Terraform template
|
|
500
|
+
route53_record = TERRAFORM_SECOND_TEMPLATE % {
|
|
501
|
+
"external_ip": external_ip,
|
|
502
|
+
"full_dns_name": full_dns_name,
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
# Append the Route 53 record to the Terraform file
|
|
506
|
+
with open(tf_path, "a") as f:
|
|
507
|
+
f.write(route53_record.lstrip())
|
|
508
|
+
|
|
509
|
+
click.echo("Added Route 53 record to Terraform configuration")
|
|
510
|
+
else:
|
|
511
|
+
# Update the existing Route 53 record
|
|
512
|
+
updated_tf = tf_content.replace(
|
|
513
|
+
'records = ["LOAD_BALANCER_DNS_PLACEHOLDER"]', f'records = ["{external_ip}"]'
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
# Also handle case where there might be another placeholder or old value
|
|
517
|
+
import re
|
|
518
|
+
|
|
519
|
+
pattern = r'records\s*=\s*\[\s*"[^"]*"\s*\]'
|
|
520
|
+
updated_tf = re.sub(pattern, f'records = ["{external_ip}"]', updated_tf)
|
|
436
521
|
|
|
437
|
-
|
|
438
|
-
|
|
522
|
+
with open(tf_path, "w") as f:
|
|
523
|
+
f.write(updated_tf.lstrip())
|
|
439
524
|
|
|
440
|
-
|
|
525
|
+
click.echo("Updated existing Route 53 record in Terraform configuration")
|
|
441
526
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
):
|
|
447
|
-
click.echo("Applying updated Terraform configuration...")
|
|
448
|
-
apply_result = subprocess.run(
|
|
449
|
-
["terraform", "-chdir=infra", "apply", "-auto-approve"],
|
|
450
|
-
capture_output=True,
|
|
451
|
-
text=True,
|
|
527
|
+
# Run terraform plan for DNS changes
|
|
528
|
+
click.echo("\nRunning Terraform plan for DNS changes...\n")
|
|
529
|
+
plan_result = subprocess.run(
|
|
530
|
+
["terraform", "-chdir=infra", "plan"], capture_output=True, text=True
|
|
452
531
|
)
|
|
453
532
|
|
|
454
|
-
if
|
|
455
|
-
click.echo("
|
|
456
|
-
click.echo(
|
|
533
|
+
if plan_result.returncode != 0:
|
|
534
|
+
click.echo("Terraform plan for DNS changes failed:")
|
|
535
|
+
click.echo(plan_result.stderr)
|
|
457
536
|
else:
|
|
458
|
-
click.echo(
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
click.echo("\nDNS record created successfully!")
|
|
471
|
-
click.echo(
|
|
472
|
-
"\nWaiting up to 5 minutes for HTTPS endpoint to become available..."
|
|
537
|
+
click.echo(plan_result.stdout)
|
|
538
|
+
|
|
539
|
+
# Apply the updated Terraform configuration
|
|
540
|
+
if not skip_dns and (
|
|
541
|
+
auto_apply_dns
|
|
542
|
+
or click.confirm("Would you like to create the DNS record in Route 53 now?")
|
|
543
|
+
):
|
|
544
|
+
click.echo("Applying updated Terraform configuration...")
|
|
545
|
+
apply_result = subprocess.run(
|
|
546
|
+
["terraform", "-chdir=infra", "apply", "-auto-approve"],
|
|
547
|
+
capture_output=True,
|
|
548
|
+
text=True,
|
|
473
549
|
)
|
|
474
550
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
551
|
+
if apply_result.returncode != 0:
|
|
552
|
+
click.echo("Failed to create DNS record:")
|
|
553
|
+
click.echo(apply_result.stderr)
|
|
554
|
+
else:
|
|
555
|
+
click.echo(apply_result.stdout)
|
|
556
|
+
click.echo("DNS record created successfully!")
|
|
557
|
+
|
|
558
|
+
# Get the DNS name from Terraform output
|
|
559
|
+
dns_output = subprocess.run(
|
|
560
|
+
["terraform", "-chdir=infra", "output", "tinybird_dns"],
|
|
561
|
+
capture_output=True,
|
|
562
|
+
text=True,
|
|
563
|
+
)
|
|
564
|
+
|
|
565
|
+
if dns_output.returncode == 0:
|
|
566
|
+
dns_name = dns_output.stdout.strip().replace('"', "")
|
|
567
|
+
click.echo("\nDNS record created successfully!")
|
|
568
|
+
click.echo(
|
|
569
|
+
"\nWaiting up to 5 minutes for HTTPS endpoint to become available..."
|
|
570
|
+
)
|
|
478
571
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
572
|
+
import time
|
|
573
|
+
|
|
574
|
+
import requests
|
|
575
|
+
|
|
576
|
+
max_attempts = 30 # 30 attempts * 10 seconds = 5 minutes
|
|
577
|
+
attempt = 0
|
|
578
|
+
while attempt < max_attempts:
|
|
579
|
+
attempt += 1
|
|
580
|
+
try:
|
|
581
|
+
response = requests.get(
|
|
582
|
+
f"https://{dns_name}", allow_redirects=False, timeout=5
|
|
583
|
+
)
|
|
584
|
+
response.raise_for_status()
|
|
585
|
+
click.echo("\n✅ HTTPS endpoint is now accessible!")
|
|
586
|
+
break
|
|
587
|
+
except requests.RequestException:
|
|
588
|
+
if attempt == max_attempts:
|
|
589
|
+
click.echo(
|
|
590
|
+
"\n⚠️ HTTPS endpoint not accessible after 5 minutes"
|
|
591
|
+
)
|
|
592
|
+
click.echo(
|
|
593
|
+
" This might be due to DNS propagation or the Load Balancer provisioning delays"
|
|
594
|
+
)
|
|
595
|
+
click.echo(
|
|
596
|
+
" Please try accessing the URL manually in a few minutes"
|
|
597
|
+
)
|
|
598
|
+
else:
|
|
599
|
+
click.echo(
|
|
600
|
+
f"Attempt {attempt}/{max_attempts}: Not ready yet, waiting 10 seconds..."
|
|
601
|
+
)
|
|
602
|
+
time.sleep(10)
|
|
603
|
+
else:
|
|
604
|
+
click.echo(
|
|
605
|
+
f"\nYour Tinybird instance should be available at: https://tinybird.{domain}"
|
|
486
606
|
)
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
607
|
+
|
|
608
|
+
# Print a summary with the endpoint URL
|
|
609
|
+
click.echo("\n" + "=" * 60)
|
|
610
|
+
click.echo("DEPLOYMENT SUMMARY".center(60))
|
|
611
|
+
click.echo("=" * 60)
|
|
612
|
+
|
|
613
|
+
if not skip_kubectl and external_ip:
|
|
614
|
+
click.echo(f"✅ Load balancer provisioned: {external_ip}")
|
|
615
|
+
|
|
616
|
+
if not skip_dns and not skip_terraform and domain:
|
|
617
|
+
# Try to get the DNS name from Terraform output
|
|
618
|
+
dns_output = subprocess.run(
|
|
619
|
+
["terraform", "-chdir=infra", "output", "tinybird_dns"], capture_output=True, text=True
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
if dns_output.returncode == 0:
|
|
623
|
+
dns_name = dns_output.stdout.strip().replace('"', "")
|
|
624
|
+
click.echo(f"✅ DNS record created: {dns_name}")
|
|
625
|
+
click.echo(f"\n🔗 Tinybird is available at: https://{dns_name}")
|
|
626
|
+
else:
|
|
627
|
+
# Fallback to constructed DNS name
|
|
628
|
+
full_dns_name = f"{dns_record}.{domain}"
|
|
629
|
+
click.echo(f"✅ DNS record created: {full_dns_name}")
|
|
630
|
+
click.echo(f"\n🔗 Tinybird is available at: https://{full_dns_name}")
|
|
631
|
+
elif not skip_kubectl and external_ip:
|
|
632
|
+
click.echo(f"\n🔗 Tinybird is available at: https://{external_ip}")
|
|
633
|
+
if domain:
|
|
634
|
+
click.echo(f"\n📝 Consider creating a DNS record: {dns_record}.{domain} → {external_ip}")
|
|
635
|
+
|
|
636
|
+
click.echo(
|
|
637
|
+
"\n📌 Note: It may take a few minutes for DNS to propagate and the HTTPS certificate to be fully provisioned."
|
|
638
|
+
)
|
|
639
|
+
click.echo("=" * 60)
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
@infra.command(name="rm")
|
|
643
|
+
@click.argument("name")
|
|
644
|
+
@click.pass_context
|
|
645
|
+
@coro
|
|
646
|
+
async def infra_rm(ctx: click.Context, name: str):
|
|
647
|
+
"""Delete an infrastructure from Tinybird"""
|
|
648
|
+
try:
|
|
649
|
+
click.echo(FeedbackManager.highlight(message=f"\n» Deleting infrastructure '{name}' from Tinybird..."))
|
|
650
|
+
client: TinyB = ctx.ensure_object(dict)["client"]
|
|
651
|
+
user_workspaces = await client.user_workspaces_with_organization()
|
|
652
|
+
admin_org_id = user_workspaces.get("organization_id")
|
|
653
|
+
if not admin_org_id:
|
|
654
|
+
raise CLIException("No organization associated to this workspace")
|
|
655
|
+
infras = await client.infra_list(admin_org_id)
|
|
656
|
+
infra_id = next((infra["id"] for infra in infras if infra["name"] == name), None)
|
|
657
|
+
if not infra_id:
|
|
658
|
+
raise CLIException(f"Infrastructure '{name}' not found")
|
|
659
|
+
await client.infra_delete(infra_id, admin_org_id)
|
|
660
|
+
click.echo(FeedbackManager.success(message=f"\n✓ Infrastructure '{name}' deleted"))
|
|
661
|
+
except Exception as e:
|
|
662
|
+
click.echo(FeedbackManager.error(message=f"✗ Error: {e}"))
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
@infra.command(name="ls")
|
|
666
|
+
@click.pass_context
|
|
667
|
+
@coro
|
|
668
|
+
async def infra_ls(ctx: click.Context):
|
|
669
|
+
"""List self-managed infrastructures"""
|
|
670
|
+
|
|
671
|
+
client: TinyB = ctx.ensure_object(dict)["client"]
|
|
672
|
+
config = CLIConfig.get_project_config()
|
|
673
|
+
user_client = config.get_client(token=config.get_user_token() or "")
|
|
674
|
+
user_workspaces = await user_client.user_workspaces_with_organization()
|
|
675
|
+
admin_org_id = user_workspaces.get("organization_id")
|
|
676
|
+
infras = await client.infra_list(organization_id=admin_org_id)
|
|
677
|
+
columns = [
|
|
678
|
+
"name",
|
|
679
|
+
"host",
|
|
680
|
+
]
|
|
681
|
+
table_human_readable = []
|
|
682
|
+
table_machine_readable = []
|
|
683
|
+
|
|
684
|
+
for infra in infras:
|
|
685
|
+
name = infra["name"]
|
|
686
|
+
host = infra["host"]
|
|
687
|
+
|
|
688
|
+
table_human_readable.append((name, host))
|
|
689
|
+
table_machine_readable.append({"name": name, "host": host})
|
|
690
|
+
|
|
691
|
+
click.echo(FeedbackManager.info(message="\n** Infras:"))
|
|
692
|
+
echo_safe_humanfriendly_tables_format_smart_table(table_human_readable, column_names=columns)
|
|
693
|
+
click.echo("\n")
|
|
@@ -49,7 +49,7 @@ async def get_tinybird_local_config(config_obj: Dict[str, Any], build: bool = Fa
|
|
|
49
49
|
logging.debug(f"Workspace used for build: {ws_name}")
|
|
50
50
|
|
|
51
51
|
user_workspaces = requests.get(
|
|
52
|
-
f"{TB_LOCAL_HOST}/
|
|
52
|
+
f"{TB_LOCAL_HOST}/v1/user/workspaces?with_organization=true&token={admin_token}"
|
|
53
53
|
).json()
|
|
54
54
|
user_org_id = user_workspaces.get("organization_id", {})
|
|
55
55
|
local_workspaces = user_workspaces.get("workspaces", [])
|
|
@@ -57,8 +57,10 @@ async def get_tinybird_local_config(config_obj: Dict[str, Any], build: bool = Fa
|
|
|
57
57
|
ws = next((ws for ws in local_workspaces if ws["name"] == ws_name), None)
|
|
58
58
|
|
|
59
59
|
if not ws:
|
|
60
|
-
await user_client.create_workspace(
|
|
61
|
-
|
|
60
|
+
await user_client.create_workspace(
|
|
61
|
+
ws_name, template=None, assign_to_organization_id=user_org_id, version="v1"
|
|
62
|
+
)
|
|
63
|
+
user_workspaces = requests.get(f"{TB_LOCAL_HOST}/v1/user/workspaces?token={admin_token}").json()
|
|
62
64
|
ws = next((ws for ws in user_workspaces["workspaces"] if ws["name"] == ws_name), None)
|
|
63
65
|
if not ws:
|
|
64
66
|
raise AuthNoTokenException()
|
tinybird/tb/modules/login.py
CHANGED
|
@@ -43,10 +43,12 @@ class AuthHandler(http.server.SimpleHTTPRequestHandler):
|
|
|
43
43
|
const searchParams = new URLSearchParams(window.location.search);
|
|
44
44
|
const code = searchParams.get('code');
|
|
45
45
|
const workspace = searchParams.get('workspace');
|
|
46
|
+
const region = searchParams.get('region');
|
|
47
|
+
const provider = searchParams.get('provider');
|
|
46
48
|
const host = "{auth_host}";
|
|
47
49
|
fetch('/?code=' + code, {{method: 'POST'}})
|
|
48
50
|
.then(() => {{
|
|
49
|
-
window.location.href = host + "/cli-login?workspace=" + workspace;
|
|
51
|
+
window.location.href = host + "/" + provider + "/" + region + "/cli-login?workspace=" + workspace;
|
|
50
52
|
}});
|
|
51
53
|
</script>
|
|
52
54
|
</body>
|
|
@@ -114,9 +116,6 @@ async def login(host: str, auth_host: str, workspace: str):
|
|
|
114
116
|
auth_event = threading.Event()
|
|
115
117
|
auth_code: list[str] = [] # Using a list to store the code, as it's mutable
|
|
116
118
|
|
|
117
|
-
if auth_host == "https://cloud.tinybird.co" and "wadus" in host:
|
|
118
|
-
auth_host = "https://cloud-wadus.tinybird.co"
|
|
119
|
-
|
|
120
119
|
def auth_callback(code):
|
|
121
120
|
auth_code.append(code)
|
|
122
121
|
auth_event.set()
|
|
@@ -132,6 +131,7 @@ async def login(host: str, auth_host: str, workspace: str):
|
|
|
132
131
|
callback_url = f"http://localhost:{AUTH_SERVER_PORT}"
|
|
133
132
|
params = {
|
|
134
133
|
"redirect_uri": callback_url,
|
|
134
|
+
"apiHost": host,
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
if workspace:
|
|
@@ -147,15 +147,14 @@ async def login(host: str, auth_host: str, workspace: str):
|
|
|
147
147
|
response = requests.get( # noqa: ASYNC210
|
|
148
148
|
f"{auth_host}/api/cli-login?{urlencode(params)}",
|
|
149
149
|
)
|
|
150
|
-
|
|
151
150
|
data = response.json()
|
|
152
151
|
cli_config = CLIConfig.get_project_config()
|
|
153
152
|
cli_config.set_token(data.get("workspace_token", ""))
|
|
154
153
|
cli_config.set_token_for_host(data.get("workspace_token", ""), host)
|
|
155
154
|
cli_config.set_user_token(data.get("user_token", ""))
|
|
156
|
-
cli_config.set_host(host)
|
|
155
|
+
cli_config.set_host(host or data.get("api_host", ""))
|
|
157
156
|
|
|
158
|
-
ws = await cli_config.get_client(token=data.get("workspace_token", "")).workspace_info()
|
|
157
|
+
ws = await cli_config.get_client(token=data.get("workspace_token", ""), host=host).workspace_info(version="v1")
|
|
159
158
|
for k in ("id", "name", "user_email", "user_id", "scope"):
|
|
160
159
|
if k in ws:
|
|
161
160
|
cli_config[k] = ws[k]
|
tinybird/tb/modules/workspace.py
CHANGED
|
@@ -47,7 +47,7 @@ async def workspace_ls(ctx: Context) -> None:
|
|
|
47
47
|
config = CLIConfig.get_project_config()
|
|
48
48
|
client: TinyB = ctx.ensure_object(dict)["client"]
|
|
49
49
|
|
|
50
|
-
response = await client.user_workspaces()
|
|
50
|
+
response = await client.user_workspaces(version="v1")
|
|
51
51
|
|
|
52
52
|
current_main_workspace = await get_current_main_workspace(config)
|
|
53
53
|
if not current_main_workspace:
|
|
@@ -184,7 +184,7 @@ async def delete_workspace(
|
|
|
184
184
|
|
|
185
185
|
user_token = await get_user_token(config, user_token)
|
|
186
186
|
|
|
187
|
-
workspaces = (await client.user_workspaces()).get("workspaces", [])
|
|
187
|
+
workspaces = (await client.user_workspaces(version="v1")).get("workspaces", [])
|
|
188
188
|
workspace_to_delete = next(
|
|
189
189
|
(
|
|
190
190
|
workspace
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
tinybird/__cli__.py,sha256=esPl5QDTzuQgHe5FuxWLm-fURFigGGwjnYLh9GuWUw4,232
|
|
2
|
-
tinybird/client.py,sha256=
|
|
2
|
+
tinybird/client.py,sha256=GeWkyx6kLSVIBxmSg4k4pCzz_jYZMW0SdbSvhjvHvs0,55059
|
|
3
3
|
tinybird/config.py,sha256=5UP_UZ2Qtlm5aOH5W7SbtN8r7X-8u3-r853joKqU5zs,6072
|
|
4
4
|
tinybird/connectors.py,sha256=7Gjms7b5MAaBFGi3xytsJurCylprONpFcYrzp4Fw2Rc,15241
|
|
5
5
|
tinybird/context.py,sha256=FfqYfrGX_I7PKGTQo93utaKPDNVYWelg4Hsp3evX5wM,1291
|
|
@@ -15,14 +15,14 @@ tinybird/syncasync.py,sha256=IPnOx6lMbf9SNddN1eBtssg8vCLHMt76SuZ6YNYm-Yk,27761
|
|
|
15
15
|
tinybird/tornado_template.py,sha256=jjNVDMnkYFWXflmT8KU_Ssbo5vR8KQq3EJMk5vYgXRw,41959
|
|
16
16
|
tinybird/ch_utils/constants.py,sha256=aYvg2C_WxYWsnqPdZB1ZFoIr8ZY-XjUXYyHKE9Ansj0,3890
|
|
17
17
|
tinybird/ch_utils/engine.py,sha256=BZuPM7MFS7vaEKK5tOMR2bwSAgJudPrJt27uVEwZmTY,40512
|
|
18
|
-
tinybird/tb/__cli__.py,sha256=
|
|
18
|
+
tinybird/tb/__cli__.py,sha256=KrzvGHoHoPrkwZ1uV5MMRrnjT-DSbzgO2X8_c_LNVJI,251
|
|
19
19
|
tinybird/tb/cli.py,sha256=H_HaZhkimKgkryYXpBjHfY9Qtg-ZORiONU3psDNpzDk,1135
|
|
20
20
|
tinybird/tb/modules/auth.py,sha256=L1IatO2arRSzys3t8px8xVt8uPWUL5EVD0sFzAV_uVU,9022
|
|
21
21
|
tinybird/tb/modules/build.py,sha256=h5drdmDFX8NHts9dA2Zepao7KSgMAl3DZGyFufVZP78,11085
|
|
22
22
|
tinybird/tb/modules/cicd.py,sha256=ldjswh70j0lQpReZGb6YbOcSIVYhrMUQFdJTfAfownU,5671
|
|
23
|
-
tinybird/tb/modules/cli.py,sha256=
|
|
24
|
-
tinybird/tb/modules/common.py,sha256=
|
|
25
|
-
tinybird/tb/modules/config.py,sha256=
|
|
23
|
+
tinybird/tb/modules/cli.py,sha256=9VrTDe6HlarG8h6wn_u7PgaRTCMZ4Hi_qiE3U2KHo2k,17912
|
|
24
|
+
tinybird/tb/modules/common.py,sha256=QhSPoLKz3J892z3qi0BBEOxLKmBU6f0MK7pmKIFIkVk,81427
|
|
25
|
+
tinybird/tb/modules/config.py,sha256=FqdLpLaKpYubqw3xkB4EX06ufZYDgGRxONR_9i-y-KE,11416
|
|
26
26
|
tinybird/tb/modules/connection.py,sha256=WKeDxbTpSsQ1PUmsT730g3S5RT2PtR5mPpVEanD1nbM,3933
|
|
27
27
|
tinybird/tb/modules/copy.py,sha256=MAVqKip8_QhOYq99U_XuqSO6hCLJEh5sFtbhcXtI3SI,5802
|
|
28
28
|
tinybird/tb/modules/create.py,sha256=VvY5C0GNM_j3ZPK4ba8yq3cephJ356PaAsBWR-zoPXs,14054
|
|
@@ -32,13 +32,13 @@ tinybird/tb/modules/endpoint.py,sha256=EhVoGAXsFz-83Fiwj1gI-I73iRRvL49d0W81un7hv
|
|
|
32
32
|
tinybird/tb/modules/exceptions.py,sha256=4A2sSjCEqKUMqpP3WI00zouCWW4uLaghXXLZBSw04mY,3363
|
|
33
33
|
tinybird/tb/modules/feedback_manager.py,sha256=7nNiOx7OMebiheLED1r0d75SbuXCNxyBmF4e20rCBNc,69511
|
|
34
34
|
tinybird/tb/modules/fmt.py,sha256=qpf9APqKTKL2uphNgdbj4OMVyLkAxZn6dn4eHF99L5g,3553
|
|
35
|
-
tinybird/tb/modules/infra.py,sha256=
|
|
35
|
+
tinybird/tb/modules/infra.py,sha256=dJcPYN_hpcLpygl4IcAANeP7fUgziwMGqYadWd9IT5g,29105
|
|
36
36
|
tinybird/tb/modules/job.py,sha256=956Pj8BEEsiD2GZsV9RKKVM3I_CveOLgS82lykO5ukk,2963
|
|
37
37
|
tinybird/tb/modules/llm.py,sha256=AC0VSphTOM2t-v1_3NLvNN_FIbgMo4dTyMqIv5nniPo,835
|
|
38
38
|
tinybird/tb/modules/llm_utils.py,sha256=nS9r4FAElJw8yXtmdYrx-rtI2zXR8qXfi1QqUDCfxvg,3469
|
|
39
39
|
tinybird/tb/modules/local.py,sha256=revEEYP-Oq5nqURgmZmc0kt1_tEjM31dyUtcH5YbOuc,5842
|
|
40
|
-
tinybird/tb/modules/local_common.py,sha256=
|
|
41
|
-
tinybird/tb/modules/login.py,sha256=
|
|
40
|
+
tinybird/tb/modules/local_common.py,sha256=RN5OEncHdq7ua4AZ--WgKtaFuEsLvIhq_ROHJadRXXA,3188
|
|
41
|
+
tinybird/tb/modules/login.py,sha256=CF6OjTuXvnD5WMpouEvSyEaMf0XTe0VE0s_y3QtyImg,6252
|
|
42
42
|
tinybird/tb/modules/logout.py,sha256=ULooy1cDBD02-r7voZmhV7udA0ML5tVuflJyShrh56Y,1022
|
|
43
43
|
tinybird/tb/modules/materialization.py,sha256=r8Q9HXcYEmfrEzP4WpiasCKDJdSkTPaAKJtZMoJKhi8,5749
|
|
44
44
|
tinybird/tb/modules/mock.py,sha256=9VKlp2bO2NsRgqF03SrFv_8OvAoHeRcOU89TiBRFfqY,3891
|
|
@@ -55,12 +55,12 @@ tinybird/tb/modules/telemetry.py,sha256=Hh2Io8ZPROSunbOLuMvuIFU4TqwWPmQTqal4WS09
|
|
|
55
55
|
tinybird/tb/modules/test.py,sha256=FUU-drY8mdjNoKsw16O5ZqvYvZqzycrZBEpSwbhGDUE,11456
|
|
56
56
|
tinybird/tb/modules/token.py,sha256=OhqLFpCHVfYeBCxJ0n7n2qoho9E9eGcUfHgL7R1MUVQ,13485
|
|
57
57
|
tinybird/tb/modules/watch.py,sha256=poNJOUNDESDNn80H2dHvE6X6pIu-t9MZFi59_TxVN2U,8822
|
|
58
|
-
tinybird/tb/modules/workspace.py,sha256=
|
|
58
|
+
tinybird/tb/modules/workspace.py,sha256=QAUihOnzao-Nyk2QM2Rhopp4oTwNh7vlKt0a52avg_E,7777
|
|
59
59
|
tinybird/tb/modules/workspace_members.py,sha256=Vb5XEaKmkfONyfg2MS5EcpwolMvv7GLwFS5m2EuobT8,8726
|
|
60
|
-
tinybird/tb/modules/datafile/build.py,sha256=
|
|
60
|
+
tinybird/tb/modules/datafile/build.py,sha256=jhfIJ2xt0N13XsLPe3iMQIyCPApHS13_Df2LfISYtT8,50952
|
|
61
61
|
tinybird/tb/modules/datafile/build_common.py,sha256=rT7VJ5mnQ68R_8US91DAtkusfvjWuG_NObOzNgtN_ko,4562
|
|
62
|
-
tinybird/tb/modules/datafile/build_datasource.py,sha256=
|
|
63
|
-
tinybird/tb/modules/datafile/build_pipe.py,sha256=
|
|
62
|
+
tinybird/tb/modules/datafile/build_datasource.py,sha256=CCU3eQ8Rax9RgHHfbAXDRL6rQ49N35h_GDQnGrUUUzA,17379
|
|
63
|
+
tinybird/tb/modules/datafile/build_pipe.py,sha256=w-Wd08gZYAEcak9FdBijVfIU2_Wn_PPdgAZddPpoGTo,11382
|
|
64
64
|
tinybird/tb/modules/datafile/common.py,sha256=i9Gvhz3JiR58MRBcYZDwqTqamQOj-46TnHU8Hm8bqmg,81399
|
|
65
65
|
tinybird/tb/modules/datafile/diff.py,sha256=-0J7PsBO64T7LOZSkZ4ZFHHCPvT7cKItnJkbz2PkndU,6754
|
|
66
66
|
tinybird/tb/modules/datafile/exceptions.py,sha256=8rw2umdZjtby85QbuRKFO5ETz_eRHwUY5l7eHsy1wnI,556
|
|
@@ -71,7 +71,7 @@ tinybird/tb/modules/datafile/format_pipe.py,sha256=58iSTrJ5lg-IsbpX8TQumQTuZ6UIo
|
|
|
71
71
|
tinybird/tb/modules/datafile/parse_datasource.py,sha256=BNof0FnaIVZUG5ORFtZSw-gUmWID4o2ZQLVgfVIuHRI,1566
|
|
72
72
|
tinybird/tb/modules/datafile/parse_pipe.py,sha256=tBjh3-I0iq7JdtB84RPYFrUlUOF2ZYWgQ_mwW5SPgmI,3467
|
|
73
73
|
tinybird/tb/modules/datafile/pipe_checker.py,sha256=LnDLGIHLJ3N7qHb2ptEbPr8CoczNfGwpjOY8EMdxfHQ,24649
|
|
74
|
-
tinybird/tb/modules/datafile/playground.py,sha256=
|
|
74
|
+
tinybird/tb/modules/datafile/playground.py,sha256=7yblQh3jlNfdY0ySSCBrXTK6mE9e0TFI-TbaGGV-qBE,56545
|
|
75
75
|
tinybird/tb/modules/datafile/pull.py,sha256=vcjMUbjnZ9XQMGmL33J3ElpbXBTat8Yzp-haeDggZd4,5967
|
|
76
76
|
tinybird/tb/modules/tinyunit/tinyunit.py,sha256=GlDgEXc6TDO3ODxgfATAL2fvbKy-b_CzqoeDrApRm0g,11715
|
|
77
77
|
tinybird/tb/modules/tinyunit/tinyunit_lib.py,sha256=hGh1ZaXC1af7rKnX7222urkj0QJMhMWclqMy59dOqwE,1922
|
|
@@ -81,8 +81,8 @@ tinybird/tb_cli_modules/config.py,sha256=IsgdtFRnUrkY8-Zo32lmk6O7u3bHie1QCxLwgp4
|
|
|
81
81
|
tinybird/tb_cli_modules/exceptions.py,sha256=pmucP4kTF4irIt7dXiG-FcnI-o3mvDusPmch1L8RCWk,3367
|
|
82
82
|
tinybird/tb_cli_modules/regions.py,sha256=QjsL5H6Kg-qr0aYVLrvb1STeJ5Sx_sjvbOYO0LrEGMk,166
|
|
83
83
|
tinybird/tb_cli_modules/telemetry.py,sha256=Hh2Io8ZPROSunbOLuMvuIFU4TqwWPmQTqal4WS09K1A,10449
|
|
84
|
-
tinybird-0.0.1.
|
|
85
|
-
tinybird-0.0.1.
|
|
86
|
-
tinybird-0.0.1.
|
|
87
|
-
tinybird-0.0.1.
|
|
88
|
-
tinybird-0.0.1.
|
|
84
|
+
tinybird-0.0.1.dev98.dist-info/METADATA,sha256=FgjtduxdgJ497kEb9unG91uWspOt1H0qdXCnnjExEms,2585
|
|
85
|
+
tinybird-0.0.1.dev98.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
86
|
+
tinybird-0.0.1.dev98.dist-info/entry_points.txt,sha256=LwdHU6TfKx4Qs7BqqtaczEZbImgU7Abe9Lp920zb_fo,43
|
|
87
|
+
tinybird-0.0.1.dev98.dist-info/top_level.txt,sha256=VqqqEmkAy7UNaD8-V51FCoMMWXjLUlR0IstvK7tJYVY,54
|
|
88
|
+
tinybird-0.0.1.dev98.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|