tinybird 0.0.1.dev93__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.
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/PKG-INFO +1 -1
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/__cli__.py +2 -2
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/cli.py +2 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/cli.py +3 -3
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/common.py +26 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/datafile/common.py +27 -5
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/deployment.py +23 -3
- tinybird-0.0.1.dev95/tinybird/tb/modules/infra.py +473 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/local_common.py +7 -2
- tinybird-0.0.1.dev95/tinybird/tb/modules/open.py +42 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird.egg-info/PKG-INFO +1 -1
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird.egg-info/SOURCES.txt +2 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/setup.cfg +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/__cli__.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/ch_utils/constants.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/ch_utils/engine.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/check_pypi.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/client.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/config.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/connectors.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/context.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/datafile.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/datatypes.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/feedback_manager.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/git_settings.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/prompts.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/sql.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/sql_template.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/sql_template_fmt.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/sql_toolset.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/syncasync.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/auth.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/build.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/cicd.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/config.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/connection.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/copy.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/create.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/datafile/build.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/datafile/build_common.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/datafile/build_datasource.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/datafile/build_pipe.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/datafile/diff.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/datafile/exceptions.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/datafile/fixture.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/datafile/format_common.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/datafile/format_datasource.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/datafile/format_pipe.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/datafile/parse_datasource.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/datafile/parse_pipe.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/datafile/pipe_checker.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/datafile/playground.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/datafile/pull.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/datasource.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/endpoint.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/exceptions.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/feedback_manager.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/fmt.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/job.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/llm.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/llm_utils.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/local.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/login.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/logout.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/materialization.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/mock.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/pipe.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/playground.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/project.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/regions.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/secret.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/shell.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/table.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/tag.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/telemetry.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/test.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/tinyunit/tinyunit.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/tinyunit/tinyunit_lib.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/token.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/watch.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/workspace.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/workspace_members.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb_cli.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/auth.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/branch.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/cicd.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/cli.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/common.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/config.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/connection.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/datasource.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/exceptions.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/fmt.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/job.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/pipe.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/regions.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/tag.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/telemetry.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/test.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/tinyunit/tinyunit.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/tinyunit/tinyunit_lib.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/workspace.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/workspace_members.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tornado_template.py +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird.egg-info/dependency_links.txt +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird.egg-info/entry_points.txt +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird.egg-info/requires.txt +0 -0
- {tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird.egg-info/top_level.txt +0 -0
|
@@ -4,5 +4,5 @@ __description__ = 'Tinybird Command Line Tool'
|
|
|
4
4
|
__url__ = 'https://www.tinybird.co/docs/cli/introduction.html'
|
|
5
5
|
__author__ = 'Tinybird'
|
|
6
6
|
__author_email__ = 'support@tinybird.co'
|
|
7
|
-
__version__ = '0.0.1.
|
|
8
|
-
__revision__ = '
|
|
7
|
+
__version__ = '0.0.1.dev95'
|
|
8
|
+
__revision__ = 'dc3767a'
|
|
@@ -15,12 +15,14 @@ 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
|
|
21
22
|
import tinybird.tb.modules.logout
|
|
22
23
|
import tinybird.tb.modules.materialization
|
|
23
24
|
import tinybird.tb.modules.mock
|
|
25
|
+
import tinybird.tb.modules.open
|
|
24
26
|
import tinybird.tb.modules.pipe
|
|
25
27
|
import tinybird.tb.modules.playground
|
|
26
28
|
import tinybird.tb.modules.secret
|
|
@@ -110,7 +110,7 @@ async def cli(
|
|
|
110
110
|
folder = os.path.join(config_temp._path.replace(".tinyb", ""), config.get("cwd", os.getcwd()))
|
|
111
111
|
project = Project(folder=folder)
|
|
112
112
|
config["path"] = str(project.path)
|
|
113
|
-
# If they have passed a token or host as
|
|
113
|
+
# If they have passed a token or host as parameter and it's different that record in .tinyb, refresh the workspace id
|
|
114
114
|
if token or host:
|
|
115
115
|
try:
|
|
116
116
|
workspace = await client.workspace_info()
|
|
@@ -393,12 +393,12 @@ def __unpatch_click_output():
|
|
|
393
393
|
|
|
394
394
|
|
|
395
395
|
async def create_ctx_client(ctx: Context, config: Dict[str, Any], cloud: bool, build: bool, staging: bool):
|
|
396
|
-
commands_without_ctx_client = ["auth", "check", "
|
|
396
|
+
commands_without_ctx_client = ["auth", "check", "local", "login", "logout", "update", "upgrade"]
|
|
397
397
|
command = ctx.invoked_subcommand
|
|
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:
|
|
@@ -2138,3 +2140,27 @@ async def get_user_token(config: CLIConfig, user_token: Optional[str] = None) ->
|
|
|
2138
2140
|
await check_user_token_with_client(client, user_token)
|
|
2139
2141
|
|
|
2140
2142
|
return user_token
|
|
2143
|
+
|
|
2144
|
+
|
|
2145
|
+
def get_ui_url(api_host: str) -> str:
|
|
2146
|
+
"""Transforms API URLs into their corresponding UI URLs.
|
|
2147
|
+
Examples:
|
|
2148
|
+
>>> get_ui_url("http://localhost:8000")
|
|
2149
|
+
'https://cloud.tinybird.co/local/8000'
|
|
2150
|
+
>>> get_ui_url("https://api.europe-west2.gcp.tinybird.co")
|
|
2151
|
+
'https://cloud.tinybird.co/gcp/europe-west2'
|
|
2152
|
+
>>> get_ui_url("https://other-domain.com")
|
|
2153
|
+
'https://other-domain.com'
|
|
2154
|
+
"""
|
|
2155
|
+
if "//localhost" in api_host:
|
|
2156
|
+
port = api_host.split(":")[-1] or "80"
|
|
2157
|
+
return f"https://cloud.tinybird.co/local/{port}"
|
|
2158
|
+
|
|
2159
|
+
if api_host.endswith("tinybird.co") and "api." in api_host:
|
|
2160
|
+
parts = api_host.split(".")
|
|
2161
|
+
if len(parts) >= 4:
|
|
2162
|
+
region = parts[1] or "europe-west2"
|
|
2163
|
+
cloud = parts[2] or "gcp"
|
|
2164
|
+
return f"https://cloud.tinybird.co/{cloud}/{region}"
|
|
2165
|
+
|
|
2166
|
+
return api_host
|
|
@@ -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
|
-
|
|
1287
|
+
lineno = kwargs["lineno"]
|
|
1278
1288
|
if len(args) < 2:
|
|
1279
1289
|
raise DatafileSyntaxError(
|
|
1280
|
-
message='TOKEN takes two params: token name and
|
|
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
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
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
|
-
|
|
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=
|
|
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
|
+
)
|
|
@@ -17,6 +17,7 @@ TB_LOCAL_HOST = f"http://localhost:{TB_LOCAL_PORT}"
|
|
|
17
17
|
|
|
18
18
|
async def get_tinybird_local_client(config_obj: Dict[str, Any], build: bool = False, staging: bool = False) -> TinyB:
|
|
19
19
|
"""Get a Tinybird client connected to the local environment."""
|
|
20
|
+
|
|
20
21
|
config = await get_tinybird_local_config(config_obj, build=build)
|
|
21
22
|
return config.get_client(host=TB_LOCAL_HOST, staging=staging)
|
|
22
23
|
|
|
@@ -40,9 +41,8 @@ async def get_tinybird_local_config(config_obj: Dict[str, Any], build: bool = Fa
|
|
|
40
41
|
default_token = tokens["workspace_admin_token"]
|
|
41
42
|
# Create a new workspace if path is provided. This is used to isolate the build in a different workspace.
|
|
42
43
|
if path:
|
|
43
|
-
folder_hash = hashlib.sha256(path.encode()).hexdigest()
|
|
44
44
|
user_client = config.get_client(host=TB_LOCAL_HOST, token=user_token)
|
|
45
|
-
ws_name =
|
|
45
|
+
ws_name = get_build_workspace_name(path) if build else config.get("name") or config_obj.get("name")
|
|
46
46
|
if not ws_name:
|
|
47
47
|
raise AuthNoTokenException()
|
|
48
48
|
|
|
@@ -74,3 +74,8 @@ async def get_tinybird_local_config(config_obj: Dict[str, Any], build: bool = Fa
|
|
|
74
74
|
|
|
75
75
|
config.set_user_token(user_token)
|
|
76
76
|
return config
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def get_build_workspace_name(path: str) -> str:
|
|
80
|
+
folder_hash = hashlib.sha256(path.encode()).hexdigest()
|
|
81
|
+
return f"Tinybird_Local_Build_{folder_hash}"
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import webbrowser
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from click import Context
|
|
5
|
+
|
|
6
|
+
from tinybird.tb.modules.cli import cli
|
|
7
|
+
from tinybird.tb.modules.common import coro, get_ui_url
|
|
8
|
+
from tinybird.tb.modules.exceptions import CLIException
|
|
9
|
+
from tinybird.tb.modules.feedback_manager import FeedbackManager
|
|
10
|
+
from tinybird.tb.modules.local_common import get_build_workspace_name
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@cli.command()
|
|
14
|
+
@click.option(
|
|
15
|
+
"--workspace",
|
|
16
|
+
help="Set the workspace you want to open. If unset, your current workspace will be used.",
|
|
17
|
+
)
|
|
18
|
+
@click.pass_context
|
|
19
|
+
@coro
|
|
20
|
+
async def open(ctx: Context, workspace: str):
|
|
21
|
+
"""Open workspace in the browser."""
|
|
22
|
+
|
|
23
|
+
config = ctx.ensure_object(dict)["config"]
|
|
24
|
+
client = ctx.ensure_object(dict)["client"]
|
|
25
|
+
env = ctx.ensure_object(dict)["env"]
|
|
26
|
+
|
|
27
|
+
url_host = get_ui_url(client.host)
|
|
28
|
+
|
|
29
|
+
if not workspace:
|
|
30
|
+
workspace = get_build_workspace_name(config.get("path")) if env == "build" else config.get("name")
|
|
31
|
+
|
|
32
|
+
if not workspace:
|
|
33
|
+
raise CLIException(
|
|
34
|
+
FeedbackManager.error(
|
|
35
|
+
message="No workspace found. Run 'tb login' first or pass a workspace using the --workspace parameter"
|
|
36
|
+
)
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
click.echo(FeedbackManager.highlight(message=f"» Opening workspace {workspace} in the browser"))
|
|
40
|
+
|
|
41
|
+
auth_url = f"{url_host}/{workspace}"
|
|
42
|
+
webbrowser.open(auth_url)
|
|
@@ -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
|
|
@@ -50,6 +51,7 @@ tinybird/tb/modules/login.py
|
|
|
50
51
|
tinybird/tb/modules/logout.py
|
|
51
52
|
tinybird/tb/modules/materialization.py
|
|
52
53
|
tinybird/tb/modules/mock.py
|
|
54
|
+
tinybird/tb/modules/open.py
|
|
53
55
|
tinybird/tb/modules/pipe.py
|
|
54
56
|
tinybird/tb/modules/playground.py
|
|
55
57
|
tinybird/tb/modules/project.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/datafile/build_datasource.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/datafile/format_datasource.py
RENAMED
|
File without changes
|
|
File without changes
|
{tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb/modules/datafile/parse_datasource.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tinybird-0.0.1.dev93 → tinybird-0.0.1.dev95}/tinybird/tb_cli_modules/tinyunit/tinyunit_lib.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|