tinybird 0.0.1.dev94__tar.gz → 0.0.1.dev95__tar.gz

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.

Files changed (108) hide show
  1. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/PKG-INFO +1 -1
  2. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/__cli__.py +2 -2
  3. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/cli.py +1 -0
  4. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/cli.py +1 -1
  5. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/common.py +2 -0
  6. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/datafile/common.py +27 -5
  7. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/deployment.py +23 -3
  8. tinybird-0.0.1.dev95/tinybird/tb/modules/infra.py +473 -0
  9. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird.egg-info/PKG-INFO +1 -1
  10. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird.egg-info/SOURCES.txt +1 -0
  11. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/setup.cfg +0 -0
  12. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/__cli__.py +0 -0
  13. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/ch_utils/constants.py +0 -0
  14. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/ch_utils/engine.py +0 -0
  15. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/check_pypi.py +0 -0
  16. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/client.py +0 -0
  17. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/config.py +0 -0
  18. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/connectors.py +0 -0
  19. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/context.py +0 -0
  20. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/datafile.py +0 -0
  21. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/datatypes.py +0 -0
  22. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/feedback_manager.py +0 -0
  23. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/git_settings.py +0 -0
  24. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/prompts.py +0 -0
  25. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/sql.py +0 -0
  26. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/sql_template.py +0 -0
  27. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/sql_template_fmt.py +0 -0
  28. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/sql_toolset.py +0 -0
  29. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/syncasync.py +0 -0
  30. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/auth.py +0 -0
  31. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/build.py +0 -0
  32. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/cicd.py +0 -0
  33. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/config.py +0 -0
  34. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/connection.py +0 -0
  35. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/copy.py +0 -0
  36. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/create.py +0 -0
  37. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/datafile/build.py +0 -0
  38. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/datafile/build_common.py +0 -0
  39. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/datafile/build_datasource.py +0 -0
  40. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/datafile/build_pipe.py +0 -0
  41. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/datafile/diff.py +0 -0
  42. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/datafile/exceptions.py +0 -0
  43. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/datafile/fixture.py +0 -0
  44. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/datafile/format_common.py +0 -0
  45. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/datafile/format_datasource.py +0 -0
  46. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/datafile/format_pipe.py +0 -0
  47. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/datafile/parse_datasource.py +0 -0
  48. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/datafile/parse_pipe.py +0 -0
  49. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/datafile/pipe_checker.py +0 -0
  50. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/datafile/playground.py +0 -0
  51. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/datafile/pull.py +0 -0
  52. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/datasource.py +0 -0
  53. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/endpoint.py +0 -0
  54. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/exceptions.py +0 -0
  55. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/feedback_manager.py +0 -0
  56. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/fmt.py +0 -0
  57. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/job.py +0 -0
  58. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/llm.py +0 -0
  59. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/llm_utils.py +0 -0
  60. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/local.py +0 -0
  61. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/local_common.py +0 -0
  62. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/login.py +0 -0
  63. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/logout.py +0 -0
  64. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/materialization.py +0 -0
  65. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/mock.py +0 -0
  66. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/open.py +0 -0
  67. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/pipe.py +0 -0
  68. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/playground.py +0 -0
  69. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/project.py +0 -0
  70. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/regions.py +0 -0
  71. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/secret.py +0 -0
  72. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/shell.py +0 -0
  73. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/table.py +0 -0
  74. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/tag.py +0 -0
  75. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/telemetry.py +0 -0
  76. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/test.py +0 -0
  77. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/tinyunit/tinyunit.py +0 -0
  78. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/tinyunit/tinyunit_lib.py +0 -0
  79. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/token.py +0 -0
  80. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/watch.py +0 -0
  81. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/workspace.py +0 -0
  82. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb/modules/workspace_members.py +0 -0
  83. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb_cli.py +0 -0
  84. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/auth.py +0 -0
  85. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/branch.py +0 -0
  86. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/cicd.py +0 -0
  87. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/cli.py +0 -0
  88. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/common.py +0 -0
  89. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/config.py +0 -0
  90. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/connection.py +0 -0
  91. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/datasource.py +0 -0
  92. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/exceptions.py +0 -0
  93. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/fmt.py +0 -0
  94. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/job.py +0 -0
  95. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/pipe.py +0 -0
  96. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/regions.py +0 -0
  97. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/tag.py +0 -0
  98. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/telemetry.py +0 -0
  99. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/test.py +0 -0
  100. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/tinyunit/tinyunit.py +0 -0
  101. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/tinyunit/tinyunit_lib.py +0 -0
  102. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/workspace.py +0 -0
  103. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/workspace_members.py +0 -0
  104. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird/tornado_template.py +0 -0
  105. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird.egg-info/dependency_links.txt +0 -0
  106. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird.egg-info/entry_points.txt +0 -0
  107. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird.egg-info/requires.txt +0 -0
  108. {tinybird-0.0.1.dev94 → tinybird-0.0.1.dev95}/tinybird.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tinybird
3
- Version: 0.0.1.dev94
3
+ Version: 0.0.1.dev95
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/cli/introduction.html
6
6
  Author: Tinybird
@@ -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.dev94'
8
- __revision__ = '97387e2'
7
+ __version__ = '0.0.1.dev95'
8
+ __revision__ = 'dc3767a'
@@ -15,6 +15,7 @@ import tinybird.tb.modules.datasource
15
15
  import tinybird.tb.modules.deployment
16
16
  import tinybird.tb.modules.endpoint
17
17
  import tinybird.tb.modules.fmt
18
+ import tinybird.tb.modules.infra
18
19
  import tinybird.tb.modules.job
19
20
  import tinybird.tb.modules.local
20
21
  import tinybird.tb.modules.login
@@ -398,7 +398,7 @@ async def create_ctx_client(ctx: Context, config: Dict[str, Any], cloud: bool, b
398
398
  if command in commands_without_ctx_client:
399
399
  return None
400
400
 
401
- commands_always_cloud = ["pull", "playground"]
401
+ commands_always_cloud = ["pull", "playground", "infra"]
402
402
  commands_always_build = ["build", "test", "dev", "create"]
403
403
  commands_always_local: List[str] = []
404
404
  if (
@@ -81,6 +81,8 @@ SUPPORTED_FORMATS = ["csv", "ndjson", "json", "parquet"]
81
81
  OLDEST_ROLLBACK = "oldest_rollback"
82
82
  MAIN_BRANCH = "main"
83
83
 
84
+ CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
85
+
84
86
 
85
87
  def obfuscate_token(value: Optional[str]) -> Optional[str]:
86
88
  if not value:
@@ -241,6 +241,11 @@ class Datafile:
241
241
  raise DatafileValidationError(f"Materialized node {repr(node['name'])} missing target datasource")
242
242
  if node.get("type", "").lower() == PipeNodeTypes.COPY:
243
243
  self.validate_copy_node(node)
244
+ for token in self.tokens:
245
+ if token["permission"].upper() != "READ":
246
+ raise DatafileValidationError(
247
+ f"Invalid permission {token['permission']} for token {token['token_name']}. Only READ is allowed for pipes"
248
+ )
244
249
  elif self.kind == DatafileKind.datasource:
245
250
  # TODO(eclbg):
246
251
  # [x] Just one node
@@ -253,6 +258,11 @@ class Datafile:
253
258
  node = self.nodes[0]
254
259
  if "schema" not in node:
255
260
  raise DatafileValidationError("SCHEMA is mandatory")
261
+ for token in self.tokens:
262
+ if token["permission"].upper() not in {"READ", "APPEND"}:
263
+ raise DatafileValidationError(
264
+ f"Invalid permission {token['permission']} for token {token['token_name']}. Only READ and APPEND are allowed for datasources"
265
+ )
256
266
  else:
257
267
  # We cannot validate a datafile whose kind is unknown
258
268
  pass
@@ -1274,16 +1284,28 @@ def parse(
1274
1284
 
1275
1285
  @multiline_not_supported
1276
1286
  def add_token(*args: str, **kwargs: Any) -> None: # token_name, permissions):
1277
- # lineno = kwargs["lineno"]
1287
+ lineno = kwargs["lineno"]
1278
1288
  if len(args) < 2:
1279
1289
  raise DatafileSyntaxError(
1280
- message='TOKEN takes two params: token name and permissions e.g TOKEN "read api token" READ',
1290
+ message='TOKEN takes two params: token name and permission e.g TOKEN "read api token" READ',
1281
1291
  lineno=lineno,
1282
1292
  pos=1,
1283
1293
  )
1284
- # TODO(eclbg): We should validate that the permissions are a valid string. We only support READ for pipes and
1285
- # APPEND for datasources
1286
- doc.tokens.append({"token_name": _unquote(args[0]), "permissions": args[1]})
1294
+ if len(args) > 2:
1295
+ raise DatafileSyntaxError(
1296
+ f"Invalid number of arguments for TOKEN command: {len(args)}. Expected 2 arguments: token name and permission",
1297
+ lineno=lineno,
1298
+ pos=len("token") + len(args[0]) + 3, # Naive handling of whitespace. Assuming there's 2
1299
+ )
1300
+ permission = args[1]
1301
+ if permission.upper() not in ["READ", "APPEND"]:
1302
+ raise DatafileSyntaxError(
1303
+ f"Invalid permission: {permission}. Only READ and APPEND are supported",
1304
+ lineno=lineno,
1305
+ pos=len("token") + len(args[0]) + 3, # Naive handling of whitespace. Assuming there's 2
1306
+ )
1307
+ token_name = _unquote(args[0])
1308
+ doc.tokens.append({"token_name": token_name, "permission": permission.upper()})
1287
1309
 
1288
1310
  @not_supported_yet()
1289
1311
  def include(*args: str, **kwargs: Any) -> None:
@@ -4,7 +4,7 @@ import sys
4
4
  import time
5
5
  from datetime import datetime
6
6
  from pathlib import Path
7
- from typing import Any, Dict, Optional, Union
7
+ from typing import Any, Dict, Optional, Tuple, Union
8
8
 
9
9
  import click
10
10
  import requests
@@ -451,8 +451,10 @@ def create_deployment(
451
451
 
452
452
  def print_changes(result: dict, project: Project) -> None:
453
453
  deployment = result.get("deployment", {})
454
- columns = ["status", "name", "path"]
454
+ resources_columns = ["status", "name", "path"]
455
455
  resources: list[list[Union[str, None]]] = []
456
+ tokens_columns = ["Change", "Token name", "Added permissions", "Removed permissions"]
457
+ tokens: list[Tuple[str, str, str, str]] = []
456
458
 
457
459
  for ds in deployment.get("new_datasource_names", []):
458
460
  resources.append(["new", ds, project.get_resource_path(ds, "datasource")])
@@ -481,8 +483,26 @@ def print_changes(result: dict, project: Project) -> None:
481
483
  for dc in deployment.get("deleted_data_connector_names", []):
482
484
  resources.append(["deleted", dc, project.get_resource_path(dc, "data_connector")])
483
485
 
486
+ for token_change in deployment.get("token_changes", []):
487
+ token_name = token_change.get("token_name")
488
+ change_type = token_change.get("change_type")
489
+ added_perms = []
490
+ removed_perms = []
491
+ permission_changes = token_change.get("permission_changes", {})
492
+ for perm in permission_changes.get("added_permissions", []):
493
+ added_perms.append(f"{perm['resource_name']}.{perm['resource_type']}:{perm['permission']}")
494
+ for perm in permission_changes.get("removed_permissions", []):
495
+ removed_perms.append(f"{perm['resource_name']}.{perm['resource_type']}:{perm['permission']}")
496
+
497
+ tokens.append((change_type, token_name, "\n".join(added_perms), "\n".join(removed_perms)))
498
+
484
499
  if resources:
485
500
  click.echo(FeedbackManager.highlight(message="\n» Changes to be deployed...\n"))
486
- echo_safe_humanfriendly_tables_format_smart_table(resources, column_names=columns)
501
+ echo_safe_humanfriendly_tables_format_smart_table(resources, column_names=resources_columns)
487
502
  else:
488
503
  click.echo(FeedbackManager.highlight(message="\n» No changes to be deployed\n"))
504
+ if tokens:
505
+ click.echo(FeedbackManager.highlight(message="\n» Changes in tokens to be deployed...\n"))
506
+ echo_safe_humanfriendly_tables_format_smart_table(tokens, column_names=tokens_columns)
507
+ else:
508
+ click.echo(FeedbackManager.highlight(message="\n» No changes in tokens to be deployed\n"))
@@ -0,0 +1,473 @@
1
+ from pathlib import Path
2
+ from typing import Optional
3
+
4
+ import click
5
+ from click import Context
6
+
7
+ from tinybird.tb.modules.cli import cli
8
+
9
+ from .common import CONTEXT_SETTINGS
10
+
11
+ K8S_YML = """
12
+ ---
13
+ apiVersion: v1
14
+ kind: Namespace
15
+ metadata:
16
+ name: %(namespace)s
17
+ labels:
18
+ name: tinybird
19
+ ---
20
+ apiVersion: v1
21
+ kind: ServiceAccount
22
+ metadata:
23
+ name: tinybird
24
+ namespace: %(namespace)s
25
+ labels:
26
+ name: tinybird
27
+ automountServiceAccountToken: true
28
+ ---
29
+ apiVersion: v1
30
+ kind: Service
31
+ metadata:
32
+ name: tinybird
33
+ namespace: %(namespace)s
34
+ labels:
35
+ name: tinybird
36
+ annotations:
37
+ service.beta.kubernetes.io/aws-load-balancer-type: 'external'
38
+ service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: 'ip'
39
+ service.beta.kubernetes.io/aws-load-balancer-backend-protocol: 'tcp'
40
+ service.beta.kubernetes.io/aws-load-balancer-scheme: 'internet-facing'
41
+ service.beta.kubernetes.io/aws-load-balancer-ssl-negotiation-policy: 'ELBSecurityPolicy-TLS13-1-2-2021-06'
42
+ service.beta.kubernetes.io/aws-load-balancer-ssl-cert: '%(cert_arn)s'
43
+ spec:
44
+ type: LoadBalancer
45
+ ports:
46
+ - port: 443
47
+ targetPort: http
48
+ protocol: TCP
49
+ name: https
50
+ selector:
51
+ name: tinybird
52
+ ---
53
+ apiVersion: apps/v1
54
+ kind: Deployment
55
+ metadata:
56
+ name: tinybird
57
+ namespace: %(namespace)s
58
+ labels:
59
+ name: tinybird
60
+ spec:
61
+ replicas: 1
62
+ selector:
63
+ matchLabels:
64
+ name: tinybird
65
+ template:
66
+ metadata:
67
+ labels:
68
+ name: tinybird
69
+ spec:
70
+ serviceAccountName: tinybird
71
+ containers:
72
+ - name: tinybird
73
+ image: "tinybirdco/tinybird-local:beta"
74
+ imagePullPolicy: Always
75
+ ports:
76
+ - name: http
77
+ containerPort: 7181
78
+ protocol: TCP
79
+ """
80
+
81
+ TERRAFORM_FIRST_TEMPLATE = """
82
+ terraform {
83
+ required_providers {
84
+ aws = {
85
+ source = "hashicorp/aws"
86
+ version = "~> 5.0"
87
+ }
88
+ }
89
+ }
90
+
91
+ provider "aws" {
92
+ region = "%(aws_region)s"
93
+ }
94
+
95
+ # Get the hosted zone data
96
+ data "aws_route53_zone" "selected" {
97
+ name = "%(domain)s"
98
+ }
99
+
100
+ # Create ACM certificate
101
+ resource "aws_acm_certificate" "cert" {
102
+ domain_name = "*.${data.aws_route53_zone.selected.name}"
103
+ validation_method = "DNS"
104
+ subject_alternative_names = [data.aws_route53_zone.selected.name]
105
+
106
+ lifecycle {
107
+ create_before_destroy = true
108
+ }
109
+ }
110
+
111
+ # Create DNS records for certificate validation
112
+ resource "aws_route53_record" "cert_validation" {
113
+ for_each = {
114
+ for dvo in aws_acm_certificate.cert.domain_validation_options : dvo.domain_name => {
115
+ name = dvo.resource_record_name
116
+ record = dvo.resource_record_value
117
+ type = dvo.resource_record_type
118
+ }
119
+ }
120
+
121
+ allow_overwrite = true
122
+ name = each.value.name
123
+ records = [each.value.record]
124
+ ttl = 60
125
+ type = each.value.type
126
+ zone_id = data.aws_route53_zone.selected.zone_id
127
+ }
128
+
129
+ # Certificate validation
130
+ resource "aws_acm_certificate_validation" "cert" {
131
+ certificate_arn = aws_acm_certificate.cert.arn
132
+ validation_record_fqdns = [for record in aws_route53_record.cert_validation : record.fqdn]
133
+ }
134
+
135
+ output "certificate_arn" {
136
+ description = "The ARN of the ACM certificate"
137
+ value = aws_acm_certificate.cert.arn
138
+ }
139
+ """
140
+
141
+ TERRAFORM_SECOND_TEMPLATE = """
142
+ # Create Route 53 record for the load balancer
143
+ resource "aws_route53_record" "tinybird" {
144
+ zone_id = data.aws_route53_zone.selected.zone_id
145
+ name = "%(full_dns_name)s"
146
+ type = "CNAME"
147
+ ttl = 300
148
+ records = ["%(external_ip)s"]
149
+ }
150
+
151
+ output "tinybird_dns" {
152
+ description = "The DNS name for Tinybird"
153
+ value = aws_route53_record.tinybird.fqdn
154
+ }
155
+ """
156
+
157
+
158
+ @cli.group(context_settings=CONTEXT_SETTINGS)
159
+ @click.pass_context
160
+ def infra(ctx: Context) -> None:
161
+ """Infra commands."""
162
+
163
+
164
+ @infra.command(name="ls")
165
+ @click.pass_context
166
+ async def infra_ls(ctx: Context) -> None:
167
+ """List infra"""
168
+
169
+
170
+ @infra.command(name="init")
171
+ @click.option("--provider", default="aws", type=str, help="Infrastructure provider (aws, gcp, azure)")
172
+ @click.option("--region", type=str, help="AWS region (for AWS provider)")
173
+ @click.option("--domain", type=str, help="Route53 domain name (for AWS provider)")
174
+ @click.option("--namespace", type=str, help="Kubernetes namespace for deployment")
175
+ @click.option("--dns-record", type=str, help="DNS record name to create (without domain, e.g. 'tinybird')")
176
+ @click.option(
177
+ "--auto-apply-terraform", is_flag=True, help="Automatically apply Terraform configuration without prompting"
178
+ )
179
+ @click.option("--auto-apply-dns", is_flag=True, help="Automatically apply DNS configuration without prompting")
180
+ @click.option(
181
+ "--auto-apply-kubectl", is_flag=True, help="Automatically apply Kubernetes configuration without prompting"
182
+ )
183
+ @click.option("--skip-terraform", is_flag=True, help="Skip Terraform configuration and application")
184
+ @click.option("--skip-kubectl", is_flag=True, help="Skip Kubernetes configuration and application")
185
+ @click.option("--skip-dns", is_flag=True, help="Skip DNS configuration and application")
186
+ @click.pass_context
187
+ def infra_init(
188
+ ctx: Context,
189
+ provider: str,
190
+ region: Optional[str] = None,
191
+ domain: Optional[str] = None,
192
+ namespace: Optional[str] = None,
193
+ dns_record: Optional[str] = None,
194
+ auto_apply_terraform: bool = False,
195
+ auto_apply_dns: bool = False,
196
+ auto_apply_kubectl: bool = False,
197
+ skip_terraform: bool = False,
198
+ skip_kubectl: bool = False,
199
+ skip_dns: bool = False,
200
+ ) -> None:
201
+ """Init infra"""
202
+ # AWS-specific Terraform template creation
203
+ if provider.lower() != "aws":
204
+ click.echo("Provider not supported yet.")
205
+ return
206
+
207
+ # Create infra directory if it doesn't exist
208
+ infra_dir = Path("infra")
209
+ infra_dir.mkdir(exist_ok=True)
210
+ yaml_path = infra_dir / "k8s.yaml"
211
+ tf_path = infra_dir / "main.tf"
212
+
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)
216
+
217
+ terraform_content = TERRAFORM_FIRST_TEMPLATE % {"aws_region": region, "domain": domain}
218
+
219
+ with open(tf_path, "w") as f:
220
+ f.write(terraform_content.lstrip())
221
+
222
+ click.echo(f"Creating Terraform configuration in {tf_path}")
223
+
224
+ # 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
+ ):
228
+ import subprocess
229
+
230
+ # Initialize Terraform
231
+ click.echo("Initializing Terraform...")
232
+ init_result = subprocess.run(["terraform", "-chdir=infra", "init"], capture_output=True, text=True)
233
+
234
+ if init_result.returncode != 0:
235
+ click.echo("Terraform initialization failed:")
236
+ click.echo(init_result.stderr)
237
+ return
238
+
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
+ )
246
+
247
+ if apply_result.returncode != 0:
248
+ click.echo("Terraform apply failed:")
249
+ click.echo(apply_result.stderr)
250
+ return
251
+
252
+ click.echo(apply_result.stdout)
253
+
254
+ # Get the certificate ARN output
255
+ output_result = subprocess.run(
256
+ ["terraform", "-chdir=infra", "output", "certificate_arn"], capture_output=True, text=True
257
+ )
258
+
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
+ )
317
+
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
+ )
323
+
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
+ )
380
+
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
386
+
387
+ click.echo(
388
+ f"Attempt {attempt}/{max_attempts}: Load balancer not ready yet, waiting 10 seconds..."
389
+ )
390
+ import time
391
+
392
+ time.sleep(10)
393
+
394
+ if external_ip:
395
+ click.echo("\n✅ Load balancer provisioned successfully!")
396
+
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...")
400
+
401
+ with open(tf_path, "r") as f:
402
+ tf_content = f.read()
403
+
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
+ )
410
+
411
+ # Create the full DNS name
412
+ full_dns_name = f"{dns_record}.{domain}"
413
+
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
+ }
419
+
420
+ # Append the Route 53 record to the Terraform file
421
+ with open(tf_path, "a") as f:
422
+ f.write(route53_record.lstrip())
423
+
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
+ )
430
+
431
+ # Also handle case where there might be another placeholder or old value
432
+ import re
433
+
434
+ pattern = r'records\s*=\s*\[\s*"[^"]*"\s*\]'
435
+ updated_tf = re.sub(pattern, f'records = ["{external_ip}"]', updated_tf)
436
+
437
+ with open(tf_path, "w") as f:
438
+ f.write(updated_tf.lstrip())
439
+
440
+ click.echo("Updated existing Route 53 record in Terraform configuration")
441
+
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,
452
+ )
453
+
454
+ if apply_result.returncode != 0:
455
+ click.echo("Failed to create DNS record:")
456
+ click.echo(apply_result.stderr)
457
+ else:
458
+ click.echo("DNS record created successfully!")
459
+
460
+ # Get the DNS name from Terraform output
461
+ dns_output = subprocess.run(
462
+ ["terraform", "-chdir=infra", "output", "tinybird_dns"],
463
+ capture_output=True,
464
+ text=True,
465
+ )
466
+
467
+ if dns_output.returncode == 0:
468
+ dns_name = dns_output.stdout.strip().replace('"', "")
469
+ click.echo(f"\nYour Tinybird instance is now available at: https://{dns_name}")
470
+ else:
471
+ click.echo(
472
+ f"\nYour Tinybird instance should be available at: https://tinybird.{domain}"
473
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tinybird
3
- Version: 0.0.1.dev94
3
+ Version: 0.0.1.dev95
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/cli/introduction.html
6
6
  Author: Tinybird
@@ -41,6 +41,7 @@ tinybird/tb/modules/endpoint.py
41
41
  tinybird/tb/modules/exceptions.py
42
42
  tinybird/tb/modules/feedback_manager.py
43
43
  tinybird/tb/modules/fmt.py
44
+ tinybird/tb/modules/infra.py
44
45
  tinybird/tb/modules/job.py
45
46
  tinybird/tb/modules/llm.py
46
47
  tinybird/tb/modules/llm_utils.py
File without changes