tinybird 0.0.1.dev117__tar.gz → 0.0.1.dev118__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 (107) hide show
  1. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/PKG-INFO +1 -1
  2. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/config.py +6 -2
  3. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/__cli__.py +2 -2
  4. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/common.py +105 -37
  5. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/connection.py +44 -12
  6. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/create.py +9 -8
  7. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/deployment.py +19 -15
  8. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/feedback_manager.py +92 -5
  9. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird.egg-info/PKG-INFO +1 -1
  10. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/setup.cfg +0 -0
  11. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/__cli__.py +0 -0
  12. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/ch_utils/constants.py +0 -0
  13. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/ch_utils/engine.py +0 -0
  14. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/check_pypi.py +0 -0
  15. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/client.py +0 -0
  16. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/connectors.py +0 -0
  17. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/context.py +0 -0
  18. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/datafile.py +0 -0
  19. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/datatypes.py +0 -0
  20. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/feedback_manager.py +0 -0
  21. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/git_settings.py +0 -0
  22. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/prompts.py +0 -0
  23. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/sql.py +0 -0
  24. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/sql_template.py +0 -0
  25. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/sql_template_fmt.py +0 -0
  26. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/sql_toolset.py +0 -0
  27. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/syncasync.py +0 -0
  28. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/cli.py +0 -0
  29. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/auth.py +0 -0
  30. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/build.py +0 -0
  31. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/cicd.py +0 -0
  32. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/cli.py +0 -0
  33. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/config.py +0 -0
  34. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/copy.py +0 -0
  35. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/datafile/build.py +0 -0
  36. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/datafile/build_common.py +0 -0
  37. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/datafile/build_datasource.py +0 -0
  38. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/datafile/build_pipe.py +0 -0
  39. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/datafile/common.py +0 -0
  40. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/datafile/diff.py +0 -0
  41. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/datafile/exceptions.py +0 -0
  42. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/datafile/fixture.py +0 -0
  43. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/datafile/format_common.py +0 -0
  44. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/datafile/format_datasource.py +0 -0
  45. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/datafile/format_pipe.py +0 -0
  46. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/datafile/parse_datasource.py +0 -0
  47. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/datafile/parse_pipe.py +0 -0
  48. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/datafile/pipe_checker.py +0 -0
  49. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/datafile/playground.py +0 -0
  50. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/datafile/pull.py +0 -0
  51. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/datasource.py +0 -0
  52. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/endpoint.py +0 -0
  53. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/exceptions.py +0 -0
  54. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/fmt.py +0 -0
  55. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/infra.py +0 -0
  56. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/job.py +0 -0
  57. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/llm.py +0 -0
  58. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/llm_utils.py +0 -0
  59. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/local.py +0 -0
  60. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/local_common.py +0 -0
  61. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/login.py +0 -0
  62. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/logout.py +0 -0
  63. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/materialization.py +0 -0
  64. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/mock.py +0 -0
  65. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/open.py +0 -0
  66. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/pipe.py +0 -0
  67. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/project.py +0 -0
  68. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/regions.py +0 -0
  69. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/secret.py +0 -0
  70. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/shell.py +0 -0
  71. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/table.py +0 -0
  72. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/tag.py +0 -0
  73. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/telemetry.py +0 -0
  74. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/test.py +0 -0
  75. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/tinyunit/tinyunit.py +0 -0
  76. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/tinyunit/tinyunit_lib.py +0 -0
  77. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/token.py +0 -0
  78. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/watch.py +0 -0
  79. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/workspace.py +0 -0
  80. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb/modules/workspace_members.py +0 -0
  81. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb_cli.py +0 -0
  82. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb_cli_modules/auth.py +0 -0
  83. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb_cli_modules/branch.py +0 -0
  84. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb_cli_modules/cicd.py +0 -0
  85. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb_cli_modules/cli.py +0 -0
  86. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb_cli_modules/common.py +0 -0
  87. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb_cli_modules/config.py +0 -0
  88. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb_cli_modules/connection.py +0 -0
  89. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb_cli_modules/datasource.py +0 -0
  90. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb_cli_modules/exceptions.py +0 -0
  91. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb_cli_modules/fmt.py +0 -0
  92. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb_cli_modules/job.py +0 -0
  93. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb_cli_modules/pipe.py +0 -0
  94. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb_cli_modules/regions.py +0 -0
  95. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb_cli_modules/tag.py +0 -0
  96. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb_cli_modules/telemetry.py +0 -0
  97. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb_cli_modules/test.py +0 -0
  98. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb_cli_modules/tinyunit/tinyunit.py +0 -0
  99. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb_cli_modules/tinyunit/tinyunit_lib.py +0 -0
  100. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb_cli_modules/workspace.py +0 -0
  101. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tb_cli_modules/workspace_members.py +0 -0
  102. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird/tornado_template.py +0 -0
  103. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird.egg-info/SOURCES.txt +0 -0
  104. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird.egg-info/dependency_links.txt +0 -0
  105. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird.egg-info/entry_points.txt +0 -0
  106. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/tinybird.egg-info/requires.txt +0 -0
  107. {tinybird-0.0.1.dev117 → tinybird-0.0.1.dev118}/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.dev117
3
+ Version: 0.0.1.dev118
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/cli/introduction.html
6
6
  Author: Tinybird
@@ -124,8 +124,12 @@ def get_display_host(ui_host: str):
124
124
  return LEGACY_HOSTS.get(ui_host, ui_host)
125
125
 
126
126
 
127
- def get_display_cloud_host(cloud_host: str):
128
- return CLOUD_HOSTS.get(cloud_host, cloud_host)
127
+ def get_display_cloud_host(api_host: str) -> str:
128
+ is_local = "localhost" in api_host
129
+ if is_local:
130
+ port = api_host.split(":")[-1]
131
+ return f"http://cloud.tinybird.co/local/{port}"
132
+ return CLOUD_HOSTS.get(api_host, api_host)
129
133
 
130
134
 
131
135
  class FeatureFlags:
@@ -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.dev117'
8
- __revision__ = 'f1a78c6'
7
+ __version__ = '0.0.1.dev118'
8
+ __revision__ = '5932fa5'
@@ -1754,27 +1754,26 @@ async def remove_release(
1754
1754
  async def run_aws_iamrole_connection_flow(
1755
1755
  client: TinyB,
1756
1756
  service: str,
1757
- policy: str = "read",
1758
- ):
1757
+ environment: str,
1758
+ ) -> Tuple[str, str, str]:
1759
1759
  if service == DataConnectorType.AMAZON_DYNAMODB:
1760
- raise NotImplementedError("DynamoDB is not supported yet")
1760
+ raise NotImplementedError("DynamoDB is not supported")
1761
1761
 
1762
- resource_type = "table" if service == DataConnectorType.AMAZON_DYNAMODB else "bucket"
1763
- resource_name = click.prompt(f"Enter the name of the {resource_type}")
1764
- validate_string_connector_param(resource_type.capitalize(), resource_name)
1762
+ bucket_name = click.prompt(
1763
+ "📦 Bucket name (specific name recommended, use '*' for unrestricted access in IAM policy)",
1764
+ prompt_suffix="\n> ",
1765
+ )
1766
+ validate_string_connector_param("Bucket", bucket_name)
1765
1767
 
1766
- resource_region = click.prompt(f"Enter the region where the {resource_type} is located")
1767
- validate_string_connector_param("Region", resource_region)
1768
+ region = click.prompt("🌐 Region (the region where the bucket is located, e.g. 'us-east-1')", prompt_suffix="\n> ")
1769
+ validate_string_connector_param("Region", region)
1768
1770
 
1769
- access_policy, trust_policy, external_id = await get_aws_iamrole_policies(client, service=service, policy=policy)
1770
- access_policy = access_policy.replace("<bucket>", resource_name)
1771
+ access_policy, trust_policy, _ = await get_aws_iamrole_policies(client, service=service, policy="read")
1772
+ access_policy = access_policy.replace("<bucket>", bucket_name)
1771
1773
 
1772
- if not click.confirm(
1773
- FeedbackManager.prompt_s3_iamrole_connection_login_aws(),
1774
- show_default=False,
1775
- prompt_suffix="Press y to continue:",
1776
- ):
1777
- sys.exit(1)
1774
+ click.echo(FeedbackManager.prompt_s3_iamrole_connection_login_aws())
1775
+ click.echo(FeedbackManager.click_enter_to_continue())
1776
+ input()
1778
1777
 
1779
1778
  access_policy_copied = True
1780
1779
  try:
@@ -1782,16 +1781,58 @@ async def run_aws_iamrole_connection_flow(
1782
1781
  except Exception:
1783
1782
  access_policy_copied = False
1784
1783
 
1785
- if not click.confirm(
1784
+ # Display message and wait for user to press Enter
1785
+ click.echo(
1786
+ FeedbackManager.prompt_s3_iamrole_connection_policy(
1787
+ access_policy=access_policy, aws_region=region, bucket=bucket_name
1788
+ )
1789
+ if access_policy_copied
1790
+ else FeedbackManager.prompt_s3_iamrole_connection_policy_not_copied(
1791
+ access_policy=access_policy, aws_region=region, bucket=bucket_name
1792
+ )
1793
+ )
1794
+ click.echo(FeedbackManager.click_enter_to_continue())
1795
+ input()
1796
+
1797
+ trust_policy_copied = True
1798
+ try:
1799
+ pyperclip.copy(trust_policy)
1800
+ except Exception:
1801
+ trust_policy_copied = False
1802
+
1803
+ role_arn = click.prompt(
1786
1804
  (
1787
- FeedbackManager.prompt_s3_iamrole_connection_policy(access_policy=access_policy)
1788
- if access_policy_copied
1789
- else FeedbackManager.prompt_s3_iamrole_connection_policy_not_copied(access_policy=access_policy)
1805
+ FeedbackManager.prompt_s3_iamrole_connection_role(
1806
+ trust_policy=trust_policy,
1807
+ aws_region=region,
1808
+ bucket=bucket_name,
1809
+ environment=environment.upper(),
1810
+ step="3",
1811
+ )
1812
+ if trust_policy_copied
1813
+ else FeedbackManager.prompt_s3_iamrole_connection_role_not_copied(
1814
+ trust_policy=trust_policy,
1815
+ aws_region=region,
1816
+ bucket=bucket_name,
1817
+ environment=environment.upper(),
1818
+ step="3",
1819
+ )
1790
1820
  ),
1791
1821
  show_default=False,
1792
- prompt_suffix="Press y to continue:",
1793
- ):
1794
- sys.exit(1)
1822
+ )
1823
+ validate_string_connector_param("Role ARN", role_arn)
1824
+
1825
+ return role_arn, region, bucket_name
1826
+
1827
+
1828
+ async def production_aws_iamrole_only(
1829
+ prod_client: TinyB,
1830
+ service: str,
1831
+ region: str,
1832
+ bucket_name: str,
1833
+ environment: str,
1834
+ ) -> Tuple[str, str, str]:
1835
+ _, trust_policy, external_id = await get_aws_iamrole_policies(prod_client, service=service, policy="read")
1795
1836
 
1796
1837
  trust_policy_copied = True
1797
1838
  try:
@@ -1799,21 +1840,29 @@ async def run_aws_iamrole_connection_flow(
1799
1840
  except Exception:
1800
1841
  trust_policy_copied = False
1801
1842
 
1802
- if not click.confirm(
1843
+ role_arn = click.prompt(
1803
1844
  (
1804
- FeedbackManager.prompt_s3_iamrole_connection_role(trust_policy=trust_policy)
1845
+ FeedbackManager.prompt_s3_iamrole_connection_role(
1846
+ trust_policy=trust_policy,
1847
+ aws_region=region,
1848
+ bucket=bucket_name,
1849
+ environment=environment.upper(),
1850
+ step="4",
1851
+ )
1805
1852
  if trust_policy_copied
1806
- else FeedbackManager.prompt_s3_iamrole_connection_role_not_copied(trust_policy=trust_policy)
1853
+ else FeedbackManager.prompt_s3_iamrole_connection_role_not_copied(
1854
+ trust_policy=trust_policy,
1855
+ aws_region=region,
1856
+ bucket=bucket_name,
1857
+ environment=environment.upper(),
1858
+ step="4",
1859
+ )
1807
1860
  ),
1808
1861
  show_default=False,
1809
- prompt_suffix="Press y to continue:",
1810
- ):
1811
- sys.exit(1)
1812
-
1813
- role_arn = click.prompt("Enter the ARN of the role you just created")
1862
+ )
1814
1863
  validate_string_connector_param("Role ARN", role_arn)
1815
1864
 
1816
- return role_arn, resource_region, external_id
1865
+ return role_arn, region, external_id
1817
1866
 
1818
1867
 
1819
1868
  async def get_aws_iamrole_policies(client: TinyB, service: str, policy: str = "write"):
@@ -1846,16 +1895,35 @@ async def get_aws_iamrole_policies(client: TinyB, service: str, policy: str = "w
1846
1895
  return json.dumps(access_policy, indent=4), json.dumps(trust_policy, indent=4), external_id
1847
1896
 
1848
1897
 
1849
- async def validate_aws_iamrole_connection_name(client: TinyB) -> str:
1898
+ def get_s3_connection_name(project_folder: str) -> str:
1850
1899
  connection_name = None
1900
+ valid_pattern = r"^[a-zA-Z][a-zA-Z0-9_]*$"
1901
+
1851
1902
  while not connection_name:
1852
- connection_name = click.prompt("Enter the name for this connection", default=None, show_default=False)
1903
+ connection_name = click.prompt(
1904
+ "🔗 Enter a name for your new Tinybird S3 connection (use alphanumeric characters, and underscores)",
1905
+ prompt_suffix="\n> ",
1906
+ show_default=True,
1907
+ )
1853
1908
  assert isinstance(connection_name, str)
1854
1909
 
1855
- if await client.get_connector(connection_name) is not None:
1856
- click.echo(FeedbackManager.info_connection_already_exists(name=connection_name))
1910
+ # Validate against invalid characters
1911
+ if not re.match(valid_pattern, connection_name):
1912
+ if not connection_name[0].isalpha():
1913
+ click.echo("Error: Connection name must start with a letter.")
1914
+ else:
1915
+ click.echo("Error: Connection name can only contain letters, numbers, and underscores.")
1857
1916
  connection_name = None
1858
- assert isinstance(connection_name, str)
1917
+ continue
1918
+
1919
+ # Check for existing connection with the same name
1920
+ project_folder_path = Path(project_folder)
1921
+ connection_files = list(project_folder_path.glob("*.connection"))
1922
+ for conn_file in connection_files:
1923
+ if conn_file.stem == connection_name:
1924
+ click.echo(FeedbackManager.error_connection_file_already_exists(name=f"{connection_name}.connection"))
1925
+ connection_name = None
1926
+ break
1859
1927
  return connection_name
1860
1928
 
1861
1929
 
@@ -3,6 +3,7 @@
3
3
  # - If it makes sense and only when strictly necessary, you can create utility functions in this file.
4
4
  # - But please, **do not** interleave utility functions and command definitions.
5
5
 
6
+ import uuid
6
7
  from typing import Any, Dict, List, Optional
7
8
 
8
9
  import click
@@ -15,10 +16,11 @@ from tinybird.tb.modules.common import (
15
16
  _get_setting_value,
16
17
  coro,
17
18
  echo_safe_humanfriendly_tables_format_smart_table,
19
+ get_s3_connection_name,
20
+ production_aws_iamrole_only,
18
21
  run_aws_iamrole_connection_flow,
19
- validate_aws_iamrole_connection_name,
20
22
  )
21
- from tinybird.tb.modules.create import generate_aws_iamrole_connection_file
23
+ from tinybird.tb.modules.create import generate_aws_iamrole_connection_file_with_secret
22
24
  from tinybird.tb.modules.feedback_manager import FeedbackManager
23
25
  from tinybird.tb.modules.project import Project
24
26
 
@@ -148,20 +150,50 @@ async def connection_create_s3(ctx: Context) -> None:
148
150
  project: Project = ctx.ensure_object(dict)["project"]
149
151
  obj: Dict[str, Any] = ctx.ensure_object(dict)
150
152
  client: TinyB = obj["client"]
153
+
151
154
  service = DataConnectorType.AMAZON_S3
152
- connection_name = await validate_aws_iamrole_connection_name(client)
153
- role_arn, region, external_id = await run_aws_iamrole_connection_flow(
155
+ click.echo(FeedbackManager.prompt_s3_connection_header())
156
+ connection_name = get_s3_connection_name(project.folder)
157
+ role_arn, region, bucket_name = await run_aws_iamrole_connection_flow(
154
158
  client,
155
159
  service=service,
156
- policy="read", # For now only read since we only support import from S3
160
+ environment=obj["env"],
161
+ )
162
+ unique_suffix = uuid.uuid4().hex[:8] # Use first 8 chars of a UUID for brevity
163
+ secret_name = f"s3_role_arn_{connection_name}_{unique_suffix}"
164
+ await client.create_secret(name=secret_name, value=role_arn)
165
+
166
+ create_in_cloud = (
167
+ click.confirm(FeedbackManager.prompt_s3_iamrole_success_cloud(), default=True)
168
+ if obj["env"] == "local"
169
+ else False
157
170
  )
158
171
 
159
- await generate_aws_iamrole_connection_file(
160
- name=connection_name, service=service, role_arn=role_arn, region=region, folder=project.folder
172
+ if create_in_cloud:
173
+ prod_config = obj["config"]
174
+ host = prod_config["host"]
175
+ token = prod_config["token"]
176
+ prod_client = TinyB(
177
+ token=token,
178
+ host=host,
179
+ staging=False,
180
+ )
181
+ prod_role_arn, _, _ = await production_aws_iamrole_only(
182
+ prod_client, service=service, region=region, bucket_name=bucket_name, environment="cloud"
183
+ )
184
+ await prod_client.create_secret(name=secret_name, value=prod_role_arn)
185
+
186
+ connection_file_path = await generate_aws_iamrole_connection_file_with_secret(
187
+ name=connection_name,
188
+ service=service,
189
+ role_arn_secret_name=secret_name,
190
+ region=region,
191
+ folder=project.folder,
161
192
  )
162
- if external_id:
163
- click.echo(
164
- FeedbackManager.success_s3_iam_connection_created(
165
- connection_name=connection_name, external_id=external_id, role_arn=role_arn
166
- )
193
+
194
+ click.echo(
195
+ FeedbackManager.prompt_s3_iamrole_success(
196
+ connection_name=connection_name,
197
+ connection_path=str(connection_file_path),
167
198
  )
199
+ )
@@ -362,26 +362,27 @@ def generate_pipe_file(name: str, content: str, folder: str) -> Path:
362
362
  return f.relative_to(folder)
363
363
 
364
364
 
365
- def generate_connection_file(name: str, content: str, folder: str) -> Path:
365
+ def generate_connection_file(name: str, content: str, folder: str, skip_feedback: bool = False) -> Path:
366
366
  base = Path(folder) / "connections"
367
367
  if not base.exists():
368
368
  base.mkdir()
369
369
  f = base / (f"{name}.connection")
370
370
  with open(f"{f}", "w") as file:
371
371
  file.write(content)
372
- click.echo(FeedbackManager.info_file_created(file=f.relative_to(folder)))
372
+ if not skip_feedback:
373
+ click.echo(FeedbackManager.info_file_created(file=f.relative_to(folder)))
373
374
  return f.relative_to(folder)
374
375
 
375
376
 
376
- async def generate_aws_iamrole_connection_file(
377
- name: str, service: str, role_arn: str, region: str, folder: str
378
- ) -> None:
377
+ async def generate_aws_iamrole_connection_file_with_secret(
378
+ name: str, service: str, role_arn_secret_name: str, region: str, folder: str
379
+ ) -> Path:
379
380
  content = f"""TYPE {service}
380
-
381
- S3_ARN {role_arn}
381
+ S3_ARN {{{{ tb_secret("{role_arn_secret_name}") }}}}
382
382
  S3_REGION {region}
383
383
  """
384
- generate_connection_file(name, content, folder)
384
+ file_path = generate_connection_file(name, content, folder, skip_feedback=True)
385
+ return file_path
385
386
 
386
387
 
387
388
  def create_rules(folder: str, source: str, agent: str):
@@ -10,7 +10,7 @@ import click
10
10
  import requests
11
11
 
12
12
  from tinybird.tb.modules.cli import cli
13
- from tinybird.tb.modules.common import echo_safe_humanfriendly_tables_format_smart_table
13
+ from tinybird.tb.modules.common import echo_safe_humanfriendly_tables_format_smart_table, get_display_cloud_host
14
14
  from tinybird.tb.modules.feedback_manager import FeedbackManager
15
15
  from tinybird.tb.modules.project import Project
16
16
 
@@ -214,8 +214,14 @@ def deployment_create(
214
214
 
215
215
 
216
216
  @deployment_group.command(name="ls")
217
+ @click.option(
218
+ "--include-deleted",
219
+ is_flag=True,
220
+ default=False,
221
+ help="Include deleted deployments. Disabled by default.",
222
+ )
217
223
  @click.pass_context
218
- def deployment_ls(ctx: click.Context) -> None:
224
+ def deployment_ls(ctx: click.Context, include_deleted: bool) -> None:
219
225
  """
220
226
  List all the deployments you have in the project.
221
227
  """
@@ -224,11 +230,12 @@ def deployment_ls(ctx: click.Context) -> None:
224
230
  TINYBIRD_API_KEY = client.token
225
231
  HEADERS = {"Authorization": f"Bearer {TINYBIRD_API_KEY}"}
226
232
  url = f"{client.host}/v1/deployments"
233
+ if include_deleted:
234
+ url += "?include_deleted=true"
227
235
 
228
236
  result = api_fetch(url, HEADERS)
229
-
230
- status_map = {"data_ready": "Ready", "failed": "Failed"}
231
- columns = ["ID", "Status", "Created at", "Live"]
237
+ status_map = {"data_ready": "Staging", "failed": "Failed", "deleted": "Deleted"}
238
+ columns = ["ID", "Status", "Created at"]
232
239
  table = []
233
240
  for deployment in result.get("deployments", []):
234
241
  if deployment.get("id") == "0":
@@ -237,12 +244,12 @@ def deployment_ls(ctx: click.Context) -> None:
237
244
  table.append(
238
245
  [
239
246
  deployment.get("id"),
240
- status_map.get(deployment.get("status"), "In progress"),
247
+ "Live" if deployment.get("live") else status_map.get(deployment.get("status", "In progress")),
241
248
  datetime.fromisoformat(deployment.get("created_at")).strftime("%Y-%m-%d %H:%M:%S"),
242
- deployment.get("live"),
243
249
  ]
244
250
  )
245
251
 
252
+ table.reverse()
246
253
  echo_safe_humanfriendly_tables_format_smart_table(table, column_names=columns)
247
254
 
248
255
 
@@ -401,14 +408,11 @@ def create_deployment(
401
408
 
402
409
  status = result.get("result")
403
410
  if status == "success":
404
- # TODO: This is a hack to show the url in the case of region is public. The URL should be returned by the API
405
- if client.host == "https://api.europe-west2.gcp.tinybird.co":
406
- click.echo(
407
- FeedbackManager.gray(message="Deployment URL: ")
408
- + FeedbackManager.info(
409
- message=f"https://cloud.tinybird.co/gcp/europe-west2/{config.get('name')}/deployments/{deployment.get('id')}"
410
- )
411
- )
411
+ host = get_display_cloud_host(client.host)
412
+ click.echo(
413
+ FeedbackManager.gray(message="Deployment URL: ")
414
+ + FeedbackManager.info(message=f"{host}/{config.get('name')}/deployments/{deployment.get('id')}")
415
+ )
412
416
 
413
417
  if wait:
414
418
  click.echo(FeedbackManager.info(message="\n✓ Deployment submitted successfully"))
@@ -496,19 +496,106 @@ class FeedbackManager:
496
496
  Ready? """
497
497
  )
498
498
 
499
- prompt_s3_iamrole_connection_login_aws = prompt_message("""[1] Log into your AWS Console\n\n""")
499
+ # S3 IAM Role Connection Messages
500
+ prompt_s3_connection_header = info_highlight_message("""
501
+ ──────────────────────────────────────────────────────────────
502
+ S3 CONNECTION SETUP
503
+ ──────────────────────────────────────────────────────────────""")
504
+
505
+ prompt_s3_iamrole_connection_login_aws = prompt_message("""
506
+ ──────────────────────────────────────────────────────────────
507
+ STEP 1: AWS AUTHENTICATION
508
+ ──────────────────────────────────────────────────────────────
509
+
510
+ Please log into your \033]8;;https://console.aws.amazon.com/\033\\AWS Console\033]8;;\033\\. We'll guide you through creating the necessary permissions.
511
+
512
+ You'll be creating a single IAM Policy and Roles (you will need one per environment, Local and Cloud) to access your S3 data. Using IAM Roles improves security by providing temporary credentials and following least privilege principles.""")
513
+
500
514
  prompt_s3_iamrole_connection_policy = prompt_message(
501
- """\n[2] Go to IAM > Policies. Create a new policy with the following permissions:\n\n{access_policy}\n\n(The policy has been copied to your clipboard)\n\n"""
515
+ """
516
+ ──────────────────────────────────────────────────────────────
517
+ STEP 2: CREATE IAM POLICY
518
+ ──────────────────────────────────────────────────────────────
519
+
520
+ 1. Go to \033]8;;https://console.aws.amazon.com/iamv2/home?region={aws_region}#/policies/create\033\\AWS IAM > Create Policy\033]8;;\033\\
521
+ 2. Select the JSON tab
522
+ 3. Paste the following policy (already copied to clipboard):
523
+
524
+ {access_policy}
525
+
526
+ 4. Name the policy something meaningful (e.g., TinybirdS3Access-{bucket})
527
+ 5. Click "Create policy"
528
+ """
502
529
  )
530
+ click_enter_to_continue = prompt_message("\nPress Enter to continue...")
531
+
503
532
  prompt_s3_iamrole_connection_policy_not_copied = prompt_message(
504
- """\n[2] Go to IAM > Policies. Create a new policy with the following permissions. Please, copy this policy and replace <bucket> with your bucket name:\n\n{access_policy}\n\n"""
533
+ """
534
+ ──────────────────────────────────────────────────────────────
535
+ STEP 2: CREATE IAM POLICY
536
+ ──────────────────────────────────────────────────────────────
537
+
538
+ 1. Go to \033]8;;https://console.aws.amazon.com/iamv2/home?region={aws_region}#/policies/create\033\\AWS IAM > Create Policy\033]8;;\033\\
539
+ 2. Select the JSON tab
540
+ 3. Copy and paste the following policy (replace <bucket> with your bucket name):
541
+
542
+ {access_policy}
543
+
544
+ 4. Name the policy something meaningful (e.g., TinybirdS3Access-{bucket})
545
+ 5. Click "Create policy"
546
+ """
505
547
  )
548
+
506
549
  prompt_s3_iamrole_connection_role = prompt_message(
507
- """\n[3] Go to IAM > Roles. Create a new IAM Role using the following custom trust policy and attach the access policy you just created in the previous step:\n\n{trust_policy}\n\n(The policy has been copied to your clipboard)\n\n"""
550
+ """
551
+ ──────────────────────────────────────────────────────────────
552
+ STEP {step}: CREATE IAM ROLE FOR {environment} ENVIRONMENT
553
+ ──────────────────────────────────────────────────────────────
554
+
555
+ 1. Go to \033]8;;https://console.aws.amazon.com/iamv2/home?region={aws_region}#/roles/create\033\\AWS IAM > Create Role\033]8;;\033\\
556
+ 2. Choose "Custom trust policy" and paste the following (already copied to clipboard):
557
+
558
+ {trust_policy}
559
+
560
+ 3. Click Next, search for and select the policy you just created
561
+ 4. Name the role something meaningful (e.g., TinybirdS3Role-{bucket}-{environment})
562
+ 5. Click "Create role"
563
+ 6. Copy the Role ARN from the role details page
564
+
565
+ Please enter the ARN of the role you just created"""
508
566
  )
567
+
568
+ prompt_s3_iamrole_success_cloud = prompt_message(
569
+ "Would you like to create this connection in the cloud environment as well?"
570
+ )
571
+
509
572
  prompt_s3_iamrole_connection_role_not_copied = prompt_message(
510
- """\n[3] Go to IAM > Roles. Create a new IAM Role using the following custom trust policy and attach the access policy you just created in the previous step:\n\n{trust_policy}\n\n"""
573
+ """
574
+ ──────────────────────────────────────────────────────────────
575
+ STEP {step}: CREATE IAM ROLE FOR {environment} ENVIRONMENT
576
+ ──────────────────────────────────────────────────────────────
577
+
578
+ 1. Go to \033]8;;https://console.aws.amazon.com/iamv2/home?region={aws_region}#/roles/create\033\\AWS IAM > Create Role\033]8;;\033\\
579
+ 2. Choose "Custom trust policy" and paste the following:
580
+
581
+ {trust_policy}
582
+
583
+ 3. Click Next, search for and select the policy you just created
584
+ 4. Name the role something meaningful (e.g., TinybirdS3Role-{bucket}-{environment})
585
+ 5. Click "Create role"
586
+ 6. Copy the Role ARN from the role details page
587
+
588
+ Please enter the ARN of the role you just created"""
511
589
  )
590
+
591
+ prompt_s3_iamrole_success = success_message("""
592
+ ✅ S3 CONNECTION CONFIGURED SUCCESSFULLY
593
+
594
+ • File created at: {connection_path}
595
+ • You can now use this connection in your Data Sources with: IMPORT_CONNECTION_NAME '{connection_name}'
596
+ • \033]8;;https://www.tinybird.co/docs/forward/get-data-in/connectors/s3\033\\Learn more about our S3 Connector\033]8;;\033\\
597
+ """)
598
+
512
599
  prompt_init_git_release_pull = prompt_message(
513
600
  "❓ Download the Data Project to continue, otherwise you can't initialize Workspace with Git. Execute '{pull_command}'?"
514
601
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tinybird
3
- Version: 0.0.1.dev117
3
+ Version: 0.0.1.dev118
4
4
  Summary: Tinybird Command Line Tool
5
5
  Home-page: https://www.tinybird.co/docs/cli/introduction.html
6
6
  Author: Tinybird