tinybird 0.0.1.dev96__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 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("/v0/user/workspaces/?with_environments=false")
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("/v0/user/workspaces/?with_environments=true")
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
- "/v0/user/workspaces/?with_environments=false&with_organization=true&with_members_and_owner=false"
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("/v0/user/workspaces/?with_environments=true&only_environments=true")
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"/v0/workspaces?name={name}"
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("/v0/workspace")
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.dev96'
8
- __revision__ = 'f1cd799'
7
+ __version__ = '0.0.1.dev98'
8
+ __revision__ = 'f7b04b3'
@@ -32,8 +32,8 @@ on:
32
32
  concurrency: ${{! github.workflow }}-${{! github.event.pull_request.number }}
33
33
 
34
34
  env:
35
- TINYBIRD_HOST: ${{ secrets.TINYBIRD_HOST }}
36
- TINYBIRD_TOKEN: ${{ secrets.TINYBIRD_TOKEN }}
35
+ TINYBIRD_HOST: ${{! secrets.TINYBIRD_HOST }}
36
+ TINYBIRD_TOKEN: ${{! secrets.TINYBIRD_TOKEN }}
37
37
 
38
38
  jobs:
39
39
  ci:
@@ -55,7 +55,7 @@ jobs:
55
55
  - name: Test project
56
56
  run: tb test run
57
57
  - name: Deployment check
58
- run: tb --cloud --host ${{ TINYBIRD_HOST }} --token ${{ TINYBIRD_TOKEN }} deploy --check
58
+ run: tb --cloud --host ${{! TINYBIRD_HOST }} --token ${{! TINYBIRD_TOKEN }} deploy --check
59
59
  """
60
60
 
61
61
 
@@ -88,7 +88,7 @@ tinybird_ci_workflow:
88
88
  - cd $CI_PROJECT_DIR/{{ data_project_dir }}
89
89
  - tb build
90
90
  - tb test run
91
- - tb --cloud --host ${{ TINYBIRD_HOST }} --token ${{ TINYBIRD_TOKEN }} deploy --check
91
+ - tb --cloud --host ${{! TINYBIRD_HOST }} --token ${{! TINYBIRD_TOKEN }} deploy --check
92
92
  services:
93
93
  - name: tinybirdco/tinybird-local:beta
94
94
  alias: tinybird-local
@@ -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"]:
@@ -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(workspace_name, starterkit, organization_id)
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":
@@ -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
  )
@@ -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.tb.modules.cli import cli
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: Deployment
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
- # Write the Terraform template
214
- region = region or click.prompt("Enter aws region", default="us-east-1", type=str)
215
- domain = domain or click.prompt("Enter route 53 domain name", type=str)
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 and (
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
- click.echo(init_result.stdout)
240
-
241
- # Apply Terraform configuration
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 apply_result.returncode != 0:
248
- click.echo("Terraform apply failed:")
249
- click.echo(apply_result.stderr)
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(apply_result.stdout)
322
+ click.echo(plan_result.stdout)
253
323
 
254
- # Get the certificate ARN output
255
- output_result = subprocess.run(
256
- ["terraform", "-chdir=infra", "output", "certificate_arn"], capture_output=True, text=True
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
- if output_result.returncode == 0:
260
- cert_arn = output_result.stdout.strip().replace('"', "")
261
-
262
- namespace = namespace or click.prompt("Enter namespace name", default="tinybird", type=str)
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
- selected_index = click.prompt(
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
- selected_context = available_contexts[selected_index - 1]
325
- click.echo(f"Selected context: {selected_context}")
326
-
327
- # Apply the configuration to the selected context
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
- if get_service_result.returncode == 0:
382
- potential_ip = get_service_result.stdout.strip().replace("'", "")
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
- click.echo(
388
- f"Attempt {attempt}/{max_attempts}: Load balancer not ready yet, waiting 10 seconds..."
389
- )
390
- import time
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
- time.sleep(10)
353
+ with open(yaml_path, "w") as f:
354
+ f.write(new_content.lstrip())
393
355
 
394
- if external_ip:
395
- click.echo("\n✅ Load balancer provisioned successfully!")
356
+ click.echo(f"Created Kubernetes configuration with certificate ARN in {yaml_path}")
396
357
 
397
- # Update the Terraform configuration with the load balancer DNS
398
- if not skip_dns and domain and tf_path.exists():
399
- click.echo("\nUpdating Terraform configuration with load balancer DNS...")
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
- with open(tf_path, "r") as f:
402
- tf_content = f.read()
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
- # Check if the Route 53 record already exists in the file
405
- if 'resource "aws_route53_record" "tinybird"' not in tf_content:
406
- # Get the DNS record name
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
- # Create the full DNS name
412
- full_dns_name = f"{dns_record}.{domain}"
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
- # Use in the Terraform template
415
- route53_record = TERRAFORM_SECOND_TEMPLATE % {
416
- "external_ip": external_ip,
417
- "full_dns_name": full_dns_name,
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
- # Append the Route 53 record to the Terraform file
421
- with open(tf_path, "a") as f:
422
- f.write(route53_record.lstrip())
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
- click.echo("Added Route 53 record to Terraform configuration")
425
- else:
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
- # Also handle case where there might be another placeholder or old value
432
- import re
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
- pattern = r'records\s*=\s*\[\s*"[^"]*"\s*\]'
435
- updated_tf = re.sub(pattern, f'records = ["{external_ip}"]', updated_tf)
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
- with open(tf_path, "w") as f:
438
- f.write(updated_tf.lstrip())
522
+ with open(tf_path, "w") as f:
523
+ f.write(updated_tf.lstrip())
439
524
 
440
- click.echo("Updated existing Route 53 record in Terraform configuration")
525
+ click.echo("Updated existing Route 53 record in Terraform configuration")
441
526
 
442
- # Apply the updated Terraform configuration
443
- if not skip_dns and (
444
- auto_apply_dns
445
- or click.confirm("Would you like to create the DNS record in Route 53 now?")
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 apply_result.returncode != 0:
455
- click.echo("Failed to create DNS record:")
456
- click.echo(apply_result.stderr)
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(apply_result.stdout)
459
- click.echo("DNS record created successfully!")
460
-
461
- # Get the DNS name from Terraform output
462
- dns_output = subprocess.run(
463
- ["terraform", "-chdir=infra", "output", "tinybird_dns"],
464
- capture_output=True,
465
- text=True,
466
- )
467
-
468
- if dns_output.returncode == 0:
469
- dns_name = dns_output.stdout.strip().replace('"', "")
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
- import time
476
-
477
- import requests
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
- max_attempts = 30 # 30 attempts * 10 seconds = 5 minutes
480
- attempt = 0
481
- while attempt < max_attempts:
482
- attempt += 1
483
- try:
484
- response = requests.get(
485
- f"https://{dns_name}", allow_redirects=False, timeout=5
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
- response.raise_for_status()
488
- click.echo("\n✅ HTTPS endpoint is now accessible!")
489
- break
490
- except requests.RequestException:
491
- if attempt == max_attempts:
492
- click.echo("\n⚠️ HTTPS endpoint not accessible after 5 minutes")
493
- click.echo(
494
- " This might be due to DNS propagation or the Load Balancer provisioning delays"
495
- )
496
- click.echo(
497
- " Please try accessing the URL manually in a few minutes"
498
- )
499
- else:
500
- click.echo(
501
- f"Attempt {attempt}/{max_attempts}: Not ready yet, waiting 10 seconds..."
502
- )
503
- time.sleep(10)
504
- else:
505
- click.echo(
506
- f"\nYour Tinybird instance should be available at: https://tinybird.{domain}"
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}/v0/user/workspaces?with_organization=true&token={admin_token}"
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(ws_name, template=None, assign_to_organization_id=user_org_id)
61
- user_workspaces = requests.get(f"{TB_LOCAL_HOST}/v0/user/workspaces?token={admin_token}").json()
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()
@@ -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]
@@ -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,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tinybird
3
- Version: 0.0.1.dev96
3
+ Version: 0.0.1.dev98
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/cli/introduction.html
6
6
  Author: Tinybird
@@ -1,5 +1,5 @@
1
1
  tinybird/__cli__.py,sha256=esPl5QDTzuQgHe5FuxWLm-fURFigGGwjnYLh9GuWUw4,232
2
- tinybird/client.py,sha256=B6FaylxfcalxJTjiMgaS1Iyn9aBRsDaphwhC5VKeK6M,54179
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=quTbjPYY2p2amkeaFu_wMFFcJ0hoA3moA2Jgy9XuEeo,251
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
- tinybird/tb/modules/cicd.py,sha256=SesVtrs7WlP1KUlSSpoDA9TFP_3gUDKlIwWTMINAY00,5665
23
- tinybird/tb/modules/cli.py,sha256=j6j9CKx4YnsRTLYdE47K8ilkoj-Xf9lq7Wm2LVgQ6Lc,17888
24
- tinybird/tb/modules/common.py,sha256=EU33c_HKU8yCrj4qfbDTHFZwHEpzZ5vBmZcPxqT15DQ,81307
25
- tinybird/tb/modules/config.py,sha256=BVZg-4f_R3vJTwCChXY2AXaH67SRk62xoP_IymquosI,11404
22
+ tinybird/tb/modules/cicd.py,sha256=ldjswh70j0lQpReZGb6YbOcSIVYhrMUQFdJTfAfownU,5671
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=TeeCcJQ_9fm_sDfZnZ8E2UaEOKaBzRHGm5KMKL01O48,20375
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=Uty8vhn4FxRASqcMldpbadKcDiOeLx4PK01Nk_SbAsY,3144
41
- tinybird/tb/modules/login.py,sha256=NB-evr7b00ChKPulX7c8YLN3EX6cr0CwALN0wroAq3o,6147
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=SYkEULv_Gg8FhnAnZspengzyT5N4w0wjsvWWZ3vy3Ho,7753
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=seGFSvmgyRrAM1-icsKBkuog3WccfGUYFTPT-xoA5W8,50940
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=VjxaKKLZhPYt3XHOyMmfoqEAWAPI5D78T-8FOaN77MY,17355
63
- tinybird/tb/modules/datafile/build_pipe.py,sha256=Tf49kZmXub45qGcePFfqGO7p-FH5eYM46DtVI3AQJEc,11358
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=mVQNSLCXpBhupI3iJqRDdE7BJtkr8JjVhHxav3pYV2E,56533
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.dev96.dist-info/METADATA,sha256=92bDicSZDCfaVpfRIa9lJaibh_qH1nEIxEd7UVvCDEQ,2585
85
- tinybird-0.0.1.dev96.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
86
- tinybird-0.0.1.dev96.dist-info/entry_points.txt,sha256=LwdHU6TfKx4Qs7BqqtaczEZbImgU7Abe9Lp920zb_fo,43
87
- tinybird-0.0.1.dev96.dist-info/top_level.txt,sha256=VqqqEmkAy7UNaD8-V51FCoMMWXjLUlR0IstvK7tJYVY,54
88
- tinybird-0.0.1.dev96.dist-info/RECORD,,
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,,