tinybird 0.0.1.dev98__py3-none-any.whl → 0.0.1.dev100__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
@@ -852,9 +852,9 @@ class TinyB:
852
852
  url = f"/v0/environments/{branch_id}/regression"
853
853
  return await self._req(url, method="POST", data=data, headers={"Content-Type": "application/json"})
854
854
 
855
- async def delete_workspace(self, id: str, hard_delete_confirmation: Optional[str]):
855
+ async def delete_workspace(self, id: str, hard_delete_confirmation: Optional[str], version: str = "v0"):
856
856
  data = {"confirmation": hard_delete_confirmation}
857
- return await self._req(f"/v0/workspaces/{id}", data, method="DELETE")
857
+ return await self._req(f"/{version}/workspaces/{id}", data, method="DELETE")
858
858
 
859
859
  async def delete_branch(self, id: str):
860
860
  return await self._req(f"/v0/environments/{id}", method="DELETE")
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.dev98'
8
- __revision__ = 'f7b04b3'
7
+ __version__ = '0.0.1.dev100'
8
+ __revision__ = 'ccb1518'
@@ -1,9 +1,12 @@
1
1
  import json
2
+ import subprocess
3
+ import time
2
4
  import uuid
3
5
  from pathlib import Path
4
6
  from typing import Optional
5
7
 
6
8
  import click
9
+ import requests
7
10
  from click import Context
8
11
 
9
12
  from tinybird.client import TinyB
@@ -40,28 +43,50 @@ metadata:
40
43
  namespace: %(namespace)s
41
44
  labels:
42
45
  name: tinybird
43
- annotations:
44
- service.beta.kubernetes.io/aws-load-balancer-type: 'external'
45
- service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: 'ip'
46
- service.beta.kubernetes.io/aws-load-balancer-backend-protocol: 'tcp'
47
- service.beta.kubernetes.io/aws-load-balancer-scheme: 'internet-facing'
48
- service.beta.kubernetes.io/aws-load-balancer-ssl-negotiation-policy: 'ELBSecurityPolicy-TLS13-1-2-2021-06'
49
- service.beta.kubernetes.io/aws-load-balancer-ssl-cert: '%(cert_arn)s'
50
46
  spec:
51
- type: LoadBalancer
47
+ type: ClusterIP
52
48
  ports:
53
- - port: 443
49
+ - port: 80
54
50
  targetPort: http
55
51
  protocol: TCP
56
- name: https
52
+ name: http
57
53
  selector:
58
54
  name: tinybird
59
55
  ---
56
+ apiVersion: networking.k8s.io/v1
57
+ kind: Ingress
58
+ metadata:
59
+ namespace: %(namespace)s
60
+ name: tinybird
61
+ annotations:
62
+ alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]'
63
+ alb.ingress.kubernetes.io/scheme: internet-facing
64
+ alb.ingress.kubernetes.io/ssl-policy: ELBSecurityPolicy-TLS13-1-2-2021-06
65
+ alb.ingress.kubernetes.io/ssl-redirect: '443'
66
+ alb.ingress.kubernetes.io/target-type: 'ip'
67
+ alb.ingress.kubernetes.io/load-balancer-name: %(namespace)s
68
+ alb.ingress.kubernetes.io/success-codes: '200,301,302'
69
+ spec:
70
+ ingressClassName: alb
71
+ tls:
72
+ - hosts:
73
+ - %(full_dns_name)s
74
+ rules:
75
+ - http:
76
+ paths:
77
+ - path: /
78
+ pathType: Prefix
79
+ backend:
80
+ service:
81
+ name: tinybird
82
+ port:
83
+ name: http
84
+ ---
60
85
  apiVersion: apps/v1
61
86
  kind: StatefulSet
62
87
  metadata:
63
88
  name: tinybird
64
- namespace: %(namespace)s
89
+ namespace: %(namespace)s
65
90
  spec:
66
91
  serviceName: "tinybird"
67
92
  replicas: 1
@@ -85,6 +110,12 @@ spec:
85
110
  env:
86
111
  - name: TB_INFRA_TOKEN
87
112
  value: "%(infra_token)s"
113
+ - name: TB_INFRA_WORKSPACE
114
+ value: "%(infra_workspace)s"
115
+ - name: TB_INFRA_ORGANIZATION
116
+ value: "%(infra_organization)s"
117
+ - name: TB_INFRA_USER
118
+ value: "%(infra_user)s"
88
119
  volumeMounts:
89
120
  - name: clickhouse-data
90
121
  mountPath: /var/lib/clickhouse
@@ -115,12 +146,12 @@ provider "aws" {
115
146
 
116
147
  # Get the hosted zone data
117
148
  data "aws_route53_zone" "selected" {
118
- name = "%(domain)s"
149
+ name = "%(dns_zone_name)s"
119
150
  }
120
151
 
121
152
  # Create ACM certificate
122
153
  resource "aws_acm_certificate" "cert" {
123
- domain_name = "*.${data.aws_route53_zone.selected.name}"
154
+ domain_name = "%(dns_record)s.${data.aws_route53_zone.selected.name}"
124
155
  validation_method = "DNS"
125
156
  subject_alternative_names = [data.aws_route53_zone.selected.name]
126
157
 
@@ -152,21 +183,23 @@ resource "aws_acm_certificate_validation" "cert" {
152
183
  certificate_arn = aws_acm_certificate.cert.arn
153
184
  validation_record_fqdns = [for record in aws_route53_record.cert_validation : record.fqdn]
154
185
  }
155
-
156
- output "certificate_arn" {
157
- description = "The ARN of the ACM certificate"
158
- value = aws_acm_certificate.cert.arn
159
- }
160
186
  """
161
187
 
162
188
  TERRAFORM_SECOND_TEMPLATE = """
163
189
  # Create Route 53 record for the load balancer
190
+ data "aws_alb" "tinybird" {
191
+ name = "%(namespace)s"
192
+ }
193
+
164
194
  resource "aws_route53_record" "tinybird" {
165
195
  zone_id = data.aws_route53_zone.selected.zone_id
166
196
  name = "%(full_dns_name)s"
167
- type = "CNAME"
168
- ttl = 300
169
- records = ["%(external_ip)s"]
197
+ type = "A"
198
+ alias {
199
+ name = data.aws_alb.tinybird.dns_name
200
+ zone_id = data.aws_alb.tinybird.zone_id
201
+ evaluate_target_health = true
202
+ }
170
203
  }
171
204
 
172
205
  output "tinybird_dns" {
@@ -184,46 +217,42 @@ def infra(ctx: Context) -> None:
184
217
 
185
218
  @infra.command(name="init")
186
219
  @click.option("--name", type=str, help="Name for identifying the self-managed infrastructure in Tinybird")
187
- @click.option("--provider", default="aws", type=str, help="Infrastructure provider (aws, gcp, azure)")
220
+ @click.option("--provider", type=str, help="Infrastructure provider (aws, gcp, azure)")
188
221
  @click.option("--region", type=str, help="AWS region (for AWS provider)")
189
- @click.option("--domain", type=str, help="Route53 domain name (for AWS provider)")
222
+ @click.option("--dns-zone-name", type=str, help="DNS zone name")
190
223
  @click.option("--namespace", type=str, help="Kubernetes namespace for deployment")
191
224
  @click.option("--dns-record", type=str, help="DNS record name to create (without domain, e.g. 'tinybird')")
225
+ @click.option("--storage-class", type=str, help="Storage class for the k8s StatefulSet")
192
226
  @click.option(
193
- "--auto-apply-terraform", is_flag=True, help="Automatically apply Terraform configuration without prompting"
194
- )
195
- @click.option("--auto-apply-dns", is_flag=True, help="Automatically apply DNS configuration without prompting")
196
- @click.option(
197
- "--auto-apply-kubectl", is_flag=True, help="Automatically apply Kubernetes configuration without prompting"
227
+ "--auto-apply", is_flag=True, help="Automatically apply Terraform and kubectl configuration without prompting"
198
228
  )
199
- @click.option("--skip-terraform", is_flag=True, help="Skip Terraform configuration and application")
200
- @click.option("--skip-kubectl", is_flag=True, help="Skip Kubernetes configuration and application")
201
- @click.option("--skip-dns", is_flag=True, help="Skip DNS configuration and application")
229
+ @click.option("--skip-apply", is_flag=True, help="Skip Terraform and kubectl configuration and application")
202
230
  @click.pass_context
203
231
  def infra_init(
204
232
  ctx: Context,
205
233
  name: str,
206
234
  provider: str,
207
235
  region: Optional[str] = None,
208
- domain: Optional[str] = None,
236
+ dns_zone_name: Optional[str] = None,
209
237
  namespace: Optional[str] = None,
210
238
  dns_record: Optional[str] = None,
211
239
  storage_class: Optional[str] = None,
212
- auto_apply_terraform: bool = False,
213
- auto_apply_dns: bool = False,
214
- auto_apply_kubectl: bool = False,
215
- skip_terraform: bool = False,
216
- skip_kubectl: bool = False,
217
- skip_dns: bool = False,
240
+ auto_apply: bool = False,
241
+ skip_apply: bool = False,
218
242
  ) -> None:
219
243
  """Init infra"""
244
+ # Check if provider is specified
245
+ if not provider:
246
+ click.echo("Error: --provider option is required. Please specify a provider (aws, gcp, azure).")
247
+ return
248
+
220
249
  # AWS-specific Terraform template creation
221
250
  if provider.lower() != "aws":
222
251
  click.echo("Provider not supported yet.")
223
252
  return
224
253
 
225
254
  # Create infra directory if it doesn't exist
226
- infra_dir = Path("infra")
255
+ infra_dir = Path(f"infra/{provider}")
227
256
  infra_dir.mkdir(exist_ok=True)
228
257
  yaml_path = infra_dir / "k8s.yaml"
229
258
  tf_path = infra_dir / "main.tf"
@@ -245,7 +274,7 @@ def infra_init(
245
274
  # Get or prompt for configuration values
246
275
  name = name or click.prompt("Enter name", type=str)
247
276
  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)
277
+ dns_zone_name = dns_zone_name or config.get("dns_zone_name") or click.prompt("Enter DNS zone name", type=str)
249
278
  namespace = (
250
279
  namespace
251
280
  or config.get("namespace")
@@ -264,7 +293,7 @@ def infra_init(
264
293
  config = {
265
294
  "provider": provider,
266
295
  "region": region,
267
- "domain": domain,
296
+ "dns_zone_name": dns_zone_name,
268
297
  "namespace": namespace,
269
298
  "dns_record": dns_record,
270
299
  "storage_class": storage_class,
@@ -280,30 +309,48 @@ def infra_init(
280
309
  user_client = cli_config.get_client(token=cli_config.get_user_token() or "")
281
310
  user_workspaces = async_to_sync(user_client.user_workspaces_with_organization)()
282
311
  admin_org_id = user_workspaces.get("organization_id")
312
+ admin_org_name = user_workspaces.get("organization_name", "")
283
313
  infras = async_to_sync(client.infra_list)(organization_id=admin_org_id)
284
314
  infra = next((infra for infra in infras if infra["name"] == name), None)
285
315
  if not infra:
286
316
  click.echo(FeedbackManager.highlight(message=f"\n» Creating infrastructure '{name}' in Tinybird..."))
287
- host = f"https://{dns_record}.{domain}"
317
+ host = f"https://{dns_record}.{dns_zone_name}"
288
318
  infra = async_to_sync(client.infra_create)(organization_id=admin_org_id, name=name, host=host)
289
319
 
290
320
  infra_token = infra["token"]
291
321
 
292
322
  # Write the Terraform template
293
- terraform_content = TERRAFORM_FIRST_TEMPLATE % {"aws_region": region, "domain": domain}
323
+ terraform_content = TERRAFORM_FIRST_TEMPLATE % {
324
+ "aws_region": region,
325
+ "dns_zone_name": dns_zone_name,
326
+ "dns_record": dns_record,
327
+ }
294
328
 
295
329
  with open(tf_path, "w") as f:
296
330
  f.write(terraform_content.lstrip())
297
331
 
298
- click.echo(f"Creating Terraform configuration in {tf_path}")
332
+ click.echo(f"Created Terraform configuration in {tf_path}")
299
333
 
300
- # Apply Terraform configuration if user confirms
301
- if not skip_terraform:
302
- import subprocess
334
+ new_content = K8S_YML % {
335
+ "namespace": namespace,
336
+ "storage_class": storage_class,
337
+ "full_dns_name": f"{dns_record}.{dns_zone_name}",
338
+ "infra_token": infra_token,
339
+ "infra_workspace": cli_config.get("name", ""),
340
+ "infra_organization": admin_org_name,
341
+ "infra_user": cli_config.get_user_email() or "",
342
+ }
343
+
344
+ with open(yaml_path, "w") as f:
345
+ f.write(new_content.lstrip())
346
+
347
+ click.echo(f"Created Kubernetes configuration in {yaml_path}")
303
348
 
349
+ # Apply Terraform configuration if user confirms
350
+ if not skip_apply:
304
351
  # Initialize Terraform
305
352
  click.echo("Initializing Terraform...")
306
- init_result = subprocess.run(["terraform", "-chdir=infra", "init"], capture_output=True, text=True)
353
+ init_result = subprocess.run(["terraform", f"-chdir={infra_dir}", "init"], capture_output=True, text=True)
307
354
 
308
355
  if init_result.returncode != 0:
309
356
  click.echo("Terraform initialization failed:")
@@ -312,7 +359,7 @@ def infra_init(
312
359
 
313
360
  # Run terraform plan first
314
361
  click.echo("\nRunning Terraform plan...\n")
315
- plan_result = subprocess.run(["terraform", "-chdir=infra", "plan"], capture_output=True, text=True)
362
+ plan_result = subprocess.run(["terraform", f"-chdir={infra_dir}", "plan"], capture_output=True, text=True)
316
363
 
317
364
  if plan_result.returncode != 0:
318
365
  click.echo("Terraform plan failed:")
@@ -322,10 +369,10 @@ def infra_init(
322
369
  click.echo(plan_result.stdout)
323
370
 
324
371
  # Apply Terraform configuration if user confirms
325
- if auto_apply_terraform or click.confirm("Would you like to apply the Terraform configuration now?"):
372
+ if auto_apply or click.confirm("Would you like to apply the Terraform configuration now?"):
326
373
  click.echo("\nApplying Terraform configuration...\n")
327
374
  apply_result = subprocess.run(
328
- ["terraform", "-chdir=infra", "apply", "-auto-approve"], capture_output=True, text=True
375
+ ["terraform", f"-chdir={infra_dir}", "apply", "-auto-approve"], capture_output=True, text=True
329
376
  )
330
377
 
331
378
  if apply_result.returncode != 0:
@@ -335,309 +382,133 @@ def infra_init(
335
382
 
336
383
  click.echo(apply_result.stdout)
337
384
 
338
- # Get the certificate ARN output
339
- output_result = subprocess.run(
340
- ["terraform", "-chdir=infra", "output", "certificate_arn"], capture_output=True, text=True
385
+ # Prompt to apply the k8s configuration
386
+ if not skip_apply and (
387
+ auto_apply or click.confirm("Would you like to apply the Kubernetes configuration now?")
388
+ ):
389
+ # Get current kubectl context
390
+ current_context_result = subprocess.run(
391
+ ["kubectl", "config", "current-context"], capture_output=True, text=True
341
392
  )
342
393
 
343
- if output_result.returncode == 0:
344
- cert_arn = output_result.stdout.strip().replace('"', "")
345
-
346
- new_content = K8S_YML % {
347
- "namespace": namespace,
348
- "cert_arn": cert_arn,
349
- "storage_class": storage_class,
350
- "infra_token": infra_token,
351
- }
352
-
353
- with open(yaml_path, "w") as f:
354
- f.write(new_content.lstrip())
355
-
356
- click.echo(f"Created Kubernetes configuration with certificate ARN in {yaml_path}")
357
-
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
363
-
364
- # Get current kubectl context
365
- current_context_result = subprocess.run(
366
- ["kubectl", "config", "current-context"], capture_output=True, text=True
367
- )
368
-
369
- current_context = (
370
- current_context_result.stdout.strip() if current_context_result.returncode == 0 else "unknown"
371
- )
372
-
373
- # Get available contexts
374
- contexts_result = subprocess.run(
375
- ["kubectl", "config", "get-contexts", "-o", "name"], capture_output=True, text=True
376
- )
377
-
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
- )
407
-
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
- )
413
-
414
- selected_context = available_contexts[selected_index - 1]
415
- click.echo(f"Selected context: {selected_context}")
416
-
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
- )
424
-
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
- }
394
+ current_context = (
395
+ current_context_result.stdout.strip() if current_context_result.returncode == 0 else "unknown"
396
+ )
397
+ # Get available contexts
398
+ contexts_result = subprocess.run(
399
+ ["kubectl", "config", "get-contexts", "-o", "name"], capture_output=True, text=True
400
+ )
504
401
 
505
- # Append the Route 53 record to the Terraform file
506
- with open(tf_path, "a") as f:
507
- f.write(route53_record.lstrip())
402
+ if contexts_result.returncode != 0:
403
+ click.echo("Failed to get kubectl contexts:")
404
+ click.echo(contexts_result.stderr)
405
+ return
508
406
 
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
- )
407
+ available_contexts = [context.strip() for context in contexts_result.stdout.splitlines() if context.strip()]
515
408
 
516
- # Also handle case where there might be another placeholder or old value
517
- import re
409
+ if not available_contexts:
410
+ click.echo("No kubectl contexts found. Please configure kubectl first.")
411
+ return
518
412
 
519
- pattern = r'records\s*=\s*\[\s*"[^"]*"\s*\]'
520
- updated_tf = re.sub(pattern, f'records = ["{external_ip}"]', updated_tf)
413
+ # Prompt user to select a context
414
+ if len(available_contexts) == 1:
415
+ selected_context = available_contexts[0]
416
+ click.echo(f"Using the only available kubectl context: {selected_context}")
417
+ else:
418
+ click.echo("\nAvailable kubectl contexts:")
419
+ for i, context in enumerate(available_contexts):
420
+ marker = " (current)" if context == current_context else ""
421
+ click.echo(f" {i + 1}. {context}{marker}")
422
+
423
+ click.echo("")
424
+ default_index = (
425
+ available_contexts.index(current_context) + 1 if current_context in available_contexts else 1
426
+ )
427
+
428
+ selected_index = click.prompt(
429
+ "Select kubectl context number to apply configuration",
430
+ type=click.IntRange(1, len(available_contexts)),
431
+ default=default_index,
432
+ )
433
+
434
+ selected_context = available_contexts[selected_index - 1]
435
+ click.echo(f"Selected context: {selected_context}")
436
+
437
+ # Apply the configuration to the selected context
438
+ click.echo(f"Applying Kubernetes configuration to context '{selected_context}'...")
439
+ apply_result = subprocess.run(
440
+ ["kubectl", "--context", selected_context, "apply", "-f", str(yaml_path)],
441
+ capture_output=True,
442
+ text=True,
443
+ )
521
444
 
522
- with open(tf_path, "w") as f:
523
- f.write(updated_tf.lstrip())
445
+ if apply_result.returncode != 0:
446
+ click.echo("Failed to apply Kubernetes configuration:")
447
+ click.echo(apply_result.stderr)
448
+ else:
449
+ click.echo("Kubernetes configuration applied successfully:")
450
+ click.echo(apply_result.stdout)
451
+
452
+ # Get the namespace from the applied configuration
453
+ namespace = None
454
+ with open(yaml_path, "r") as f:
455
+ for line in f:
456
+ if "namespace:" in line and not namespace:
457
+ namespace = line.split("namespace:")[1].strip()
458
+ break
459
+
460
+ if not namespace:
461
+ namespace = "tinybird" # Default namespace
462
+
463
+ click.echo("\nWaiting for load balancer and DNS to be provisioned...")
464
+
465
+ max_attempts = 30 # 30 attempts * 10 seconds = 5 minutes
466
+ endpoint_url = f"https://{dns_record}.{dns_zone_name}"
467
+
468
+ with click.progressbar(
469
+ range(max_attempts),
470
+ label=f"Checking endpoint availability: {endpoint_url}",
471
+ length=max_attempts,
472
+ show_eta=True,
473
+ show_percent=True,
474
+ fill_char="█",
475
+ empty_char="░",
476
+ ) as bar:
477
+ for attempt in bar:
478
+ try:
479
+ response = requests.get(endpoint_url, allow_redirects=False, timeout=5)
480
+ if response.status_code < 400: # Consider any non-error response as success
481
+ click.echo(click.style("\n✅ HTTPS endpoint is now accessible!", fg="green", bold=True))
482
+ break
483
+ except requests.RequestException:
484
+ pass
485
+
486
+ if attempt == max_attempts - 1:
487
+ click.echo(
488
+ click.style(
489
+ "\n⚠️ HTTPS endpoint not accessible after 5 minutes", fg="yellow", bold=True
490
+ )
491
+ )
492
+ click.echo(
493
+ " This might be due to DNS propagation or the Load Balancer provisioning delays"
494
+ )
495
+ click.echo(f" Please try accessing {endpoint_url} manually in a few minutes")
496
+ else:
497
+ time.sleep(10)
524
498
 
525
- click.echo("Updated existing Route 53 record in Terraform configuration")
499
+ if not skip_apply:
500
+ # Print a summary with the endpoint URL
501
+ click.echo("\n" + "=" * 60)
502
+ click.echo("DEPLOYMENT SUMMARY".center(60))
503
+ click.echo("=" * 60)
504
+ click.echo("✅ Load balancer provisioned")
526
505
 
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
531
- )
506
+ click.echo(f"\n🔗 Tinybird is available at: https://{dns_record}.{dns_zone_name}")
532
507
 
533
- if plan_result.returncode != 0:
534
- click.echo("Terraform plan for DNS changes failed:")
535
- click.echo(plan_result.stderr)
536
- else:
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,
549
- )
550
-
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
- )
571
-
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}"
606
- )
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
508
+ click.echo(
509
+ "\n📌 Note: It may take a few minutes for DNS to propagate and the HTTPS certificate to be fully provisioned."
620
510
  )
621
511
 
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
512
 
642
513
  @infra.command(name="rm")
643
514
  @click.argument("name")
@@ -147,6 +147,7 @@ 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
+
150
151
  data = response.json()
151
152
  cli_config = CLIConfig.get_project_config()
152
153
  cli_config.set_token(data.get("workspace_token", ""))
@@ -203,7 +203,7 @@ async def delete_workspace(
203
203
  client.token = user_token
204
204
 
205
205
  try:
206
- await client.delete_workspace(workspace_to_delete["id"], confirm_hard_delete)
206
+ await client.delete_workspace(workspace_to_delete["id"], confirm_hard_delete, version="v1")
207
207
  click.echo(FeedbackManager.success_workspace_deleted(workspace_name=workspace_to_delete["name"]))
208
208
  except Exception as e:
209
209
  raise CLIWorkspaceException(FeedbackManager.error_exception(error=str(e)))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tinybird
3
- Version: 0.0.1.dev98
3
+ Version: 0.0.1.dev100
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=GeWkyx6kLSVIBxmSg4k4pCzz_jYZMW0SdbSvhjvHvs0,55059
2
+ tinybird/client.py,sha256=aDDifEWkVxJcm4WYaw6uUgXhav-e7JUW1N-mgtEjV4A,55087
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,7 +15,7 @@ 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=KrzvGHoHoPrkwZ1uV5MMRrnjT-DSbzgO2X8_c_LNVJI,251
18
+ tinybird/tb/__cli__.py,sha256=5rOoJNw0w56lLAZpNCTlWF_nWVfCJL-SxcTE0FJGiG4,252
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
@@ -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=dJcPYN_hpcLpygl4IcAANeP7fUgziwMGqYadWd9IT5g,29105
35
+ tinybird/tb/modules/infra.py,sha256=l64FN62AB6N_ICisEonVsI_uQ_hzwZRBvupBjBJKac8,19707
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
40
  tinybird/tb/modules/local_common.py,sha256=RN5OEncHdq7ua4AZ--WgKtaFuEsLvIhq_ROHJadRXXA,3188
41
- tinybird/tb/modules/login.py,sha256=CF6OjTuXvnD5WMpouEvSyEaMf0XTe0VE0s_y3QtyImg,6252
41
+ tinybird/tb/modules/login.py,sha256=dAaBBn_ZIQRm9BFl6Uw_HgZa2qoLuAO6mBOxob_17ms,6253
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,7 +55,7 @@ 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=QAUihOnzao-Nyk2QM2Rhopp4oTwNh7vlKt0a52avg_E,7777
58
+ tinybird/tb/modules/workspace.py,sha256=GUXSROaPn2LpR2ZTYLIlj-wZnAbiFkib9m3Cag-Kbeg,7791
59
59
  tinybird/tb/modules/workspace_members.py,sha256=Vb5XEaKmkfONyfg2MS5EcpwolMvv7GLwFS5m2EuobT8,8726
60
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
@@ -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.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,,
84
+ tinybird-0.0.1.dev100.dist-info/METADATA,sha256=QaYp1xwcpo3MDoYY8ZGLSXI9ZHFKCV6I6bk2d9u6hRg,2586
85
+ tinybird-0.0.1.dev100.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
86
+ tinybird-0.0.1.dev100.dist-info/entry_points.txt,sha256=LwdHU6TfKx4Qs7BqqtaczEZbImgU7Abe9Lp920zb_fo,43
87
+ tinybird-0.0.1.dev100.dist-info/top_level.txt,sha256=VqqqEmkAy7UNaD8-V51FCoMMWXjLUlR0IstvK7tJYVY,54
88
+ tinybird-0.0.1.dev100.dist-info/RECORD,,