oblako 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. oblako/__init__.py +3 -0
  2. oblako/banner.py +64 -0
  3. oblako/cli.py +530 -0
  4. oblako/config.py +68 -0
  5. oblako/dashboard/__init__.py +1 -0
  6. oblako/dashboard/api.py +1912 -0
  7. oblako/dashboard/frontend/.gitignore +25 -0
  8. oblako/dashboard/frontend/README.md +16 -0
  9. oblako/dashboard/frontend/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  10. oblako/dashboard/frontend/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  11. oblako/dashboard/frontend/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  12. oblako/dashboard/frontend/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  13. oblako/dashboard/frontend/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  14. oblako/dashboard/frontend/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  15. oblako/dashboard/frontend/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  16. oblako/dashboard/frontend/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  17. oblako/dashboard/frontend/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  18. oblako/dashboard/frontend/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  19. oblako/dashboard/frontend/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  20. oblako/dashboard/frontend/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  21. oblako/dashboard/frontend/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  22. oblako/dashboard/frontend/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  23. oblako/dashboard/frontend/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  24. oblako/dashboard/frontend/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  25. oblako/dashboard/frontend/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  26. oblako/dashboard/frontend/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  27. oblako/dashboard/frontend/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  28. oblako/dashboard/frontend/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  29. oblako/dashboard/frontend/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  30. oblako/dashboard/frontend/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  31. oblako/dashboard/frontend/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  32. oblako/dashboard/frontend/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  33. oblako/dashboard/frontend/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  34. oblako/dashboard/frontend/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  35. oblako/dashboard/frontend/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  36. oblako/dashboard/frontend/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  37. oblako/dashboard/frontend/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  38. oblako/dashboard/frontend/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  39. oblako/dashboard/frontend/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  40. oblako/dashboard/frontend/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  41. oblako/dashboard/frontend/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  42. oblako/dashboard/frontend/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  43. oblako/dashboard/frontend/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  44. oblako/dashboard/frontend/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  45. oblako/dashboard/frontend/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  46. oblako/dashboard/frontend/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  47. oblako/dashboard/frontend/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  48. oblako/dashboard/frontend/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  49. oblako/dashboard/frontend/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  50. oblako/dashboard/frontend/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  51. oblako/dashboard/frontend/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  52. oblako/dashboard/frontend/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  53. oblako/dashboard/frontend/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  54. oblako/dashboard/frontend/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  55. oblako/dashboard/frontend/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  56. oblako/dashboard/frontend/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  57. oblako/dashboard/frontend/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  58. oblako/dashboard/frontend/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  59. oblako/dashboard/frontend/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  60. oblako/dashboard/frontend/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  61. oblako/dashboard/frontend/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  62. oblako/dashboard/frontend/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  63. oblako/dashboard/frontend/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  64. oblako/dashboard/frontend/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  65. oblako/dashboard/frontend/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  66. oblako/dashboard/frontend/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  67. oblako/dashboard/frontend/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  68. oblako/dashboard/frontend/dist/assets/duckdb-browser-DQUTRvrj.js +2 -0
  69. oblako/dashboard/frontend/dist/assets/index-CIhx4uB3.js +401 -0
  70. oblako/dashboard/frontend/dist/assets/index-CLqonV80.css +1 -0
  71. oblako/dashboard/frontend/dist/favicon.svg +1 -0
  72. oblako/dashboard/frontend/dist/icons.svg +24 -0
  73. oblako/dashboard/frontend/dist/index.html +14 -0
  74. oblako/dashboard/frontend/public/favicon.svg +1 -0
  75. oblako/dashboard/frontend/public/icons.svg +24 -0
  76. oblako/engines/__init__.py +4 -0
  77. oblako/engines/agentcore/__init__.py +108 -0
  78. oblako/engines/appconfig/__init__.py +85 -0
  79. oblako/engines/appconfig/agent.py +176 -0
  80. oblako/engines/appconfig/app.py +280 -0
  81. oblako/engines/appconfig/engine.py +302 -0
  82. oblako/engines/appconfig/rule_evaluator.py +305 -0
  83. oblako/engines/athena/__init__.py +85 -0
  84. oblako/engines/athena/app.py +394 -0
  85. oblako/engines/athena/catalogs.py +44 -0
  86. oblako/engines/athena/ctas.py +131 -0
  87. oblako/engines/athena/workgroups.py +167 -0
  88. oblako/engines/bedrock/__init__.py +1 -0
  89. oblako/engines/bedrock/adapter.py +299 -0
  90. oblako/engines/bedrock/backends.py +211 -0
  91. oblako/engines/bedrock/eventstream.py +45 -0
  92. oblako/engines/bedrock/foundation_models.py +190 -0
  93. oblako/engines/bedrock/guardrails.py +174 -0
  94. oblako/engines/bedrock/jobs.py +197 -0
  95. oblako/engines/bedrock/models.py +142 -0
  96. oblako/engines/bedrock/ollama_client.py +75 -0
  97. oblako/engines/bedrock_runtime/__init__.py +63 -0
  98. oblako/engines/bedrock_runtime/app.py +388 -0
  99. oblako/engines/cloudformation/__init__.py +80 -0
  100. oblako/engines/cloudformation/app.py +361 -0
  101. oblako/engines/cloudformation/engine.py +601 -0
  102. oblako/engines/cloudformation/providers.py +645 -0
  103. oblako/engines/cloudformation/transform.py +113 -0
  104. oblako/engines/dynamodb_vectors/__init__.py +92 -0
  105. oblako/engines/dynamodb_vectors/app.py +685 -0
  106. oblako/engines/ecs_control/__init__.py +76 -0
  107. oblako/engines/ecs_control/app.py +136 -0
  108. oblako/engines/ecs_metadata/__init__.py +149 -0
  109. oblako/engines/eventbridge/__init__.py +84 -0
  110. oblako/engines/eventbridge/app.py +503 -0
  111. oblako/engines/firehose/__init__.py +90 -0
  112. oblako/engines/firehose/app.py +829 -0
  113. oblako/engines/glue_catalog/__init__.py +788 -0
  114. oblako/engines/glue_catalog/crawlers.py +942 -0
  115. oblako/engines/glue_catalog/expression.py +182 -0
  116. oblako/engines/glue_catalog/jobs.py +297 -0
  117. oblako/engines/glue_catalog/store.py +287 -0
  118. oblako/engines/glue_catalog/workflows.py +647 -0
  119. oblako/engines/host.py +133 -0
  120. oblako/engines/iam/__init__.py +5 -0
  121. oblako/engines/iam/evaluator.py +98 -0
  122. oblako/engines/identity.py +68 -0
  123. oblako/engines/lambda_shim/__init__.py +142 -0
  124. oblako/engines/mlflow/Dockerfile +27 -0
  125. oblako/engines/mlflow/__init__.py +9 -0
  126. oblako/engines/mwaa/__init__.py +81 -0
  127. oblako/engines/mwaa/app.py +176 -0
  128. oblako/engines/mwaa/environments.py +694 -0
  129. oblako/engines/rds_control/__init__.py +85 -0
  130. oblako/engines/rds_control/app.py +277 -0
  131. oblako/engines/rds_control/instances.py +409 -0
  132. oblako/engines/rds_data/__init__.py +67 -0
  133. oblako/engines/rds_data/app.py +147 -0
  134. oblako/engines/rds_data/executor.py +288 -0
  135. oblako/engines/redshift_control/__init__.py +85 -0
  136. oblako/engines/redshift_control/app.py +269 -0
  137. oblako/engines/redshift_control/clusters.py +356 -0
  138. oblako/engines/redshift_control/credentials.py +135 -0
  139. oblako/engines/redshift_control/serverless.py +377 -0
  140. oblako/engines/redshift_data/__init__.py +71 -0
  141. oblako/engines/redshift_data/app.py +264 -0
  142. oblako/engines/redshift_data/executor.py +373 -0
  143. oblako/engines/redshift_ml/__init__.py +85 -0
  144. oblako/engines/s3_ext/__init__.py +738 -0
  145. oblako/engines/s3_ext/inventory.py +218 -0
  146. oblako/engines/s3_ext/notifications.py +308 -0
  147. oblako/engines/s3tables/__init__.py +485 -0
  148. oblako/engines/s3tables/iceberg.py +185 -0
  149. oblako/engines/s3vectors/__init__.py +447 -0
  150. oblako/engines/sagemaker/__init__.py +74 -0
  151. oblako/engines/sagemaker/app.py +649 -0
  152. oblako/engines/sagemaker/executor.py +1997 -0
  153. oblako/engines/sagemaker/stubs.py +113 -0
  154. oblako/images/notebook/Dockerfile +23 -0
  155. oblako/images/notebook/aws-config +6 -0
  156. oblako/images/redshift/Dockerfile +91 -0
  157. oblako/images/redshift/entrypoint.sh +84 -0
  158. oblako/images/redshift/ext/Makefile +4 -0
  159. oblako/images/redshift/ext/oblako_redshift.c +80 -0
  160. oblako/images/redshift/initdb.d/00_extensions.sql +14 -0
  161. oblako/images/redshift/initdb.d/01_system_tables.sql +32 -0
  162. oblako/images/redshift/initdb.d/02_functions.sql +56 -0
  163. oblako/images/redshift/initdb.d/03_date_functions.sql +137 -0
  164. oblako/images/redshift/initdb.d/04_template1_date_functions.sql +5 -0
  165. oblako/images/redshift/initdb.d/05_catalog_views.sql +207 -0
  166. oblako/images/redshift/initdb.d/06_propagate_catalog_views.sql +16 -0
  167. oblako/images/redshift/initdb.d/07_copy_unload.sql +41 -0
  168. oblako/images/redshift/initdb.d/08_super.sql +72 -0
  169. oblako/images/redshift/initdb.d/09_redshift_ml.sql +97 -0
  170. oblako/images/redshift/initdb.d/10_propagate_redshift_ml.sql +7 -0
  171. oblako/images/redshift/initdb.d/11_integer_avg.sql +117 -0
  172. oblako/images/redshift/initdb.d/12_password_auth.sh +11 -0
  173. oblako/images/redshift/initdb.d/13_iceberg.sql +271 -0
  174. oblako/images/redshift/initdb.d/99_system_catalog.sql +172 -0
  175. oblako/images/redshift/ml_train/Dockerfile +10 -0
  176. oblako/images/redshift/ml_train/train.py +300 -0
  177. oblako/images/redshift/proxy/copy_unload.py +895 -0
  178. oblako/images/redshift/proxy/datepart.py +76 -0
  179. oblako/images/redshift/proxy/iceberg_tables.py +1477 -0
  180. oblako/images/redshift/proxy/integer_avg.py +86 -0
  181. oblako/images/redshift/proxy/listagg.py +130 -0
  182. oblako/images/redshift/proxy/pivot_unpivot.py +127 -0
  183. oblako/images/redshift/proxy/redshift_ml.py +966 -0
  184. oblako/images/redshift/proxy/redshift_proxy.py +800 -0
  185. oblako/images/redshift/proxy/super_nav.py +329 -0
  186. oblako/images/redshift-cluster/000_citus.sql +3 -0
  187. oblako/images/redshift-cluster/13_svv_table_info.sql +80 -0
  188. oblako/images/redshift-cluster/Dockerfile +55 -0
  189. oblako/images/redshift-cluster/cluster-entrypoint.sh +46 -0
  190. oblako/notebook.py +299 -0
  191. oblako/ports.py +89 -0
  192. oblako/profile.py +160 -0
  193. oblako/services/__init__.py +39 -0
  194. oblako/services/appconfig.py +62 -0
  195. oblako/services/awslambda.py +106 -0
  196. oblako/services/backends.py +730 -0
  197. oblako/services/base.py +145 -0
  198. oblako/services/bedrock.py +139 -0
  199. oblako/services/boto.py +79 -0
  200. oblako/services/caddy.py +102 -0
  201. oblako/services/cloudformation.py +50 -0
  202. oblako/services/dynamodb.py +67 -0
  203. oblako/services/ec2.py +210 -0
  204. oblako/services/ecs.py +640 -0
  205. oblako/services/elbv2.py +256 -0
  206. oblako/services/glue.py +260 -0
  207. oblako/services/glue_assets/S3ProxyCompatInterceptor.java +89 -0
  208. oblako/services/glue_catalog.py +40 -0
  209. oblako/services/iam.py +130 -0
  210. oblako/services/iceberg.py +108 -0
  211. oblako/services/kinesis.py +55 -0
  212. oblako/services/mlflow.py +170 -0
  213. oblako/services/moto.py +76 -0
  214. oblako/services/opensearch.py +72 -0
  215. oblako/services/platform.py +150 -0
  216. oblako/services/rds.py +240 -0
  217. oblako/services/redshift.py +502 -0
  218. oblako/services/s3proxy.py +267 -0
  219. oblako/services/sagemaker.py +460 -0
  220. oblako/services/sfn_templates.py +454 -0
  221. oblako/services/stepfunctions.py +102 -0
  222. oblako/services/trino.py +156 -0
  223. oblako-0.1.0.dist-info/METADATA +205 -0
  224. oblako-0.1.0.dist-info/RECORD +228 -0
  225. oblako-0.1.0.dist-info/WHEEL +4 -0
  226. oblako-0.1.0.dist-info/entry_points.txt +2 -0
  227. oblako-0.1.0.dist-info/licenses/LICENSE +202 -0
  228. oblako-0.1.0.dist-info/licenses/NOTICE +11 -0
oblako/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """oblako: Local AWS platform."""
2
+
3
+ __version__ = "0.1.0"
oblako/banner.py ADDED
@@ -0,0 +1,64 @@
1
+ """The oblako banner: the name in block letters, in oblako blue.
2
+
3
+ Shown by a bare ``oblako`` and at the start of ``oblako up``, on a terminal only, so
4
+ CI logs and pipes stay plain. ``NO_COLOR`` (https://no-color.org) prints it without
5
+ color.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import os
11
+ import sys
12
+ from typing import TextIO
13
+
14
+ from oblako import __version__
15
+
16
+ # figlet's "ANSI Shadow" font
17
+ ART = """\
18
+ ██████╗ ██████╗ ██╗ █████╗ ██╗ ██╗ ██████╗
19
+ ██╔═══██╗██╔══██╗██║ ██╔══██╗██║ ██╔╝██╔═══██╗
20
+ ██║ ██║██████╔╝██║ ███████║█████╔╝ ██║ ██║
21
+ ██║ ██║██╔══██╗██║ ██╔══██║██╔═██╗ ██║ ██║
22
+ ╚██████╔╝██████╔╝███████╗██║ ██║██║ ██╗╚██████╔╝
23
+ ╚═════╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝"""
24
+
25
+ TAGLINE = "real behavior, simulated topology"
26
+
27
+ BLUE = (0x49, 0xA0, 0xF8) # oblako blue: the letters
28
+ SHADOW = (0x2B, 0x5E, 0x93) # the same blue, darker: the box-drawing shadow
29
+ MUTED = (0x8A, 0x94, 0xA3) # the tagline
30
+
31
+
32
+ def _rgb(color: tuple[int, int, int]) -> str:
33
+ return "\033[38;2;{};{};{}m".format(*color)
34
+
35
+
36
+ RESET = "\033[0m"
37
+
38
+
39
+ def render(color: bool = True) -> str:
40
+ """Return the banner, with 24-bit color codes unless ``color`` is false."""
41
+ tagline = f"{TAGLINE} · v{__version__}"
42
+ if not color:
43
+ return f"{ART}\n{tagline}\n"
44
+ lines = []
45
+ for line in ART.splitlines():
46
+ out, current = [], None
47
+ for ch in line:
48
+ want = BLUE if ch == "█" else SHADOW if ch.strip() else None
49
+ if want and want != current:
50
+ out.append(_rgb(want))
51
+ current = want
52
+ out.append(ch)
53
+ lines.append("".join(out) + RESET)
54
+ return "\n".join(lines) + f"\n{_rgb(MUTED)}{tagline}{RESET}\n"
55
+
56
+
57
+ def show(stream: TextIO | None = None) -> None:
58
+ """Print the banner if the stream is a terminal; in color unless NO_COLOR is set."""
59
+ stream = stream or sys.stdout
60
+ if not stream.isatty():
61
+ return
62
+ stream.write(render(color=not os.environ.get("NO_COLOR")))
63
+ stream.write("\n")
64
+ stream.flush()
oblako/cli.py ADDED
@@ -0,0 +1,530 @@
1
+ """oblako CLI: manage local AWS ML services.
2
+
3
+ Usage:
4
+ oblako up Start all services
5
+ oblako up <service> Start one service, e.g. s3 or s3vectors
6
+ oblako down Stop all services
7
+ oblako status Show service status
8
+ oblako dashboard Start web dashboard (http://localhost:8000)
9
+ oblako logs <service> Show logs for a service
10
+ oblako pull <model> Pull a model into the engine (default: qwen2.5:0.5b)
11
+ oblako models List available Ollama models
12
+ oblako test Run unit tests
13
+ oblako test-integration Run integration tests
14
+ """
15
+
16
+ import argparse
17
+ import importlib.util
18
+ import sys
19
+
20
+ from oblako import banner, ports
21
+ from oblako.engines import host
22
+ from oblako.services.backends import PortInUseError
23
+ from oblako.services.platform import Oblako
24
+
25
+
26
+ def _check_docker():
27
+ """Verify Docker is reachable."""
28
+ import docker
29
+ import docker.errors
30
+
31
+ try:
32
+ docker.from_env().ping()
33
+ # requests' connection errors are OSErrors
34
+ except (docker.errors.DockerException, OSError):
35
+ print("Error: Docker is not running. Start Docker and try again.")
36
+ sys.exit(1)
37
+
38
+
39
+ # -----------------------------------------------------------------------------------------------
40
+ # Service Commands
41
+ # -----------------------------------------------------------------------------------------------
42
+ def cmd_up(args):
43
+ """Start all services, or a single named service."""
44
+ if args.service in host.ENGINES: # an in-process engine: no container
45
+ try:
46
+ url = host.start(args.service)
47
+ except RuntimeError as err:
48
+ print(f"Error: {err}")
49
+ sys.exit(1)
50
+ print(
51
+ f"{args.service} is running on {url} (logs: {host.logfile(args.service)})"
52
+ )
53
+ return
54
+ _check_docker()
55
+ if not args.service:
56
+ banner.show()
57
+ oblako = Oblako()
58
+ if args.service:
59
+ svc = _get_service(oblako, args.service)
60
+ try:
61
+ svc.start()
62
+ except PortInUseError as err:
63
+ print(f"Error: {err}")
64
+ sys.exit(1)
65
+ readiness = {svc.name: svc.wait_ready(timeout=args.timeout)}
66
+ else:
67
+ try:
68
+ oblako.up()
69
+ except PortInUseError as err:
70
+ print(f"Error: {err}")
71
+ sys.exit(1)
72
+ print("Waiting for services...")
73
+ readiness = oblako.wait_ready(timeout=args.timeout)
74
+ for name, ready in readiness.items():
75
+ print(f"{name}: {'ready' if ready else 'not ready'}")
76
+ # A CI step that starts services must fail when one never became ready
77
+ if not all(readiness.values()):
78
+ sys.exit(1)
79
+
80
+
81
+ def cmd_down(args):
82
+ """Stop all services, or a single named service."""
83
+ if args.service in host.ENGINES:
84
+ stopped = host.stop(args.service)
85
+ print(
86
+ f"{args.service} stopped"
87
+ if stopped
88
+ else f"{args.service} was not started by oblako up"
89
+ )
90
+ return
91
+ _check_docker()
92
+ oblako = Oblako()
93
+ if args.service:
94
+ svc = _get_service(oblako, args.service)
95
+ svc.stop()
96
+ else:
97
+ oblako.down()
98
+
99
+
100
+ def cmd_status(args):
101
+ """Print the status of all user-facing services."""
102
+ _check_docker()
103
+ oblako = Oblako()
104
+ statuses = oblako.status()
105
+ for name in host.ENGINES: # in-process engines; sagemaker also has a count
106
+ state, extra = host.status(name), statuses.get(name)
107
+ statuses[name] = f"{state} ({extra})" if extra and extra != "idle" else state
108
+ for name, state in statuses.items():
109
+ print(f" {name}: {state}")
110
+
111
+
112
+ def cmd_logs(args):
113
+ """Print recent logs for a named service."""
114
+ if args.service in host.ENGINES:
115
+ log = host.logfile(args.service)
116
+ lines = log.read_text(errors="replace").splitlines() if log.exists() else []
117
+ print("\n".join(lines[-args.tail :]))
118
+ return
119
+ _check_docker()
120
+ oblako = Oblako()
121
+ svc = _get_service(oblako, args.service)
122
+ print(svc.logs(tail=args.tail))
123
+
124
+
125
+ # -----------------------------------------------------------------------------------------------
126
+ # Model Commands
127
+ # -----------------------------------------------------------------------------------------------
128
+ def cmd_pull(args):
129
+ """Pull a model into the Ollama engine."""
130
+ _check_docker()
131
+ from oblako.engines.bedrock.models import DEFAULT_MODEL
132
+
133
+ oblako = Oblako()
134
+ model = args.model or DEFAULT_MODEL
135
+ print(f"Pulling {model}...")
136
+ oblako.bedrock.pull_model(model)
137
+
138
+
139
+ def cmd_models(args):
140
+ """List models available in the local Ollama engine."""
141
+ _check_docker()
142
+ oblako = Oblako()
143
+ models = oblako.bedrock.list_models()
144
+ if models:
145
+ for m in models:
146
+ print(f" {m}")
147
+ else:
148
+ print("No models. Run: oblako pull")
149
+
150
+
151
+ # -----------------------------------------------------------------------------------------------
152
+ # Dashboard
153
+ # -----------------------------------------------------------------------------------------------
154
+ def cmd_dashboard(args):
155
+ """Start the web dashboard and open it in the default browser."""
156
+ _check_docker()
157
+ import subprocess
158
+ import webbrowser
159
+
160
+ port = args.port or ports.DASHBOARD
161
+ if args.host != "127.0.0.1":
162
+ # the dashboard has no login, and it can read buckets and run SQL
163
+ print(
164
+ f"Warning: the dashboard will listen on {args.host}, so other machines "
165
+ "can reach it without a login."
166
+ )
167
+ print(f"Starting oblako dashboard on http://localhost:{port}")
168
+ webbrowser.open(f"http://localhost:{port}")
169
+ subprocess.call(
170
+ [
171
+ sys.executable,
172
+ "-m",
173
+ "uvicorn",
174
+ "oblako.dashboard.api:app",
175
+ "--host",
176
+ args.host,
177
+ "--port",
178
+ str(port),
179
+ ]
180
+ )
181
+
182
+
183
+ # Notebook
184
+ def cmd_notebook(args):
185
+ """Launch JupyterLab pre-wired so unmodified boto3 hits oblako's local services."""
186
+ if importlib.util.find_spec("jupyterlab") is None:
187
+ print(
188
+ "JupyterLab isn't installed. Install the extra: pip install 'oblako[notebook]'"
189
+ )
190
+ sys.exit(1)
191
+ from oblako import notebook
192
+
193
+ port = args.port or ports.NOTEBOOK
194
+ print(f"Launching JupyterLab on http://localhost:{port}")
195
+ print(
196
+ "kernel is pre-wired: boto3.client('s3') etc. hit oblako (run 'oblako up' for the services)"
197
+ )
198
+ sys.exit(notebook.launch(port=port, workdir=args.dir))
199
+
200
+
201
+ # -----------------------------------------------------------------------------------------------
202
+ # Redshift Data API
203
+ # -----------------------------------------------------------------------------------------------
204
+ def cmd_redshift_data(args):
205
+ """Run the Redshift Data API server (boto3 'redshift-data' endpoint)."""
206
+ import uvicorn
207
+
208
+ port = args.port or ports.REDSHIFT_DATA
209
+ print(f"Starting Redshift Data API on http://localhost:{port}")
210
+ print(" point boto3 at it: boto3.client('redshift-data', endpoint_url=...)")
211
+ from oblako.engines.identity import identify
212
+ from oblako.engines.redshift_data.app import app
213
+
214
+ uvicorn.run(identify(app, "redshift_data"), host="0.0.0.0", port=port)
215
+
216
+
217
+ def cmd_bedrock_runtime(args):
218
+ """Run the Bedrock Runtime server (boto3 'bedrock-runtime' endpoint -> Ollama)."""
219
+ import uvicorn
220
+
221
+ port = args.port or ports.BEDROCK_RUNTIME
222
+ print(f"Starting Bedrock Runtime on http://localhost:{port}")
223
+ print(" point boto3 at it: boto3.client('bedrock-runtime', endpoint_url=...)")
224
+ from oblako.engines.bedrock_runtime.app import app
225
+ from oblako.engines.identity import identify
226
+
227
+ uvicorn.run(identify(app, "bedrock_runtime"), host="0.0.0.0", port=port)
228
+
229
+
230
+ def cmd_rds_data(args):
231
+ """Run the RDS Data API server (boto3 'rds-data' endpoint -> RDS engine)."""
232
+ import uvicorn
233
+
234
+ port = args.port or ports.RDS_DATA
235
+ print(f"Starting RDS Data API on http://localhost:{port}")
236
+ print(" point boto3 at it: boto3.client('rds-data', endpoint_url=...)")
237
+ from oblako.engines.identity import identify
238
+ from oblako.engines.rds_data.app import app
239
+
240
+ uvicorn.run(identify(app, "rds_data"), host="0.0.0.0", port=port)
241
+
242
+
243
+ # CloudFormation
244
+ def cmd_cloudformation(args):
245
+ """Run the CloudFormation server (boto3 'cloudformation' endpoint -> oblako engines).
246
+
247
+ Long-lived so `aws cloudformation deploy` / `sam deploy` can target it:
248
+ export AWS_ENDPOINT_URL_CLOUDFORMATION=http://localhost:8017
249
+ """
250
+ import uvicorn
251
+
252
+ port = args.port or ports.CLOUDFORMATION
253
+ print(f"Starting CloudFormation on http://localhost:{port}")
254
+ print("point the AWS CLI / SAM at it:")
255
+ print(f"export AWS_ENDPOINT_URL_CLOUDFORMATION=http://localhost:{port}")
256
+ print(
257
+ "supported resources: S3::Bucket, DynamoDB::Table, Redshift::Cluster, RDS::DBInstance"
258
+ )
259
+ from oblako.engines.cloudformation.app import app
260
+ from oblako.engines.identity import identify
261
+
262
+ uvicorn.run(identify(app, "cloudformation"), host="0.0.0.0", port=port)
263
+
264
+
265
+ # -----------------------------------------------------------------------------------------------
266
+ # Bedrock AgentCore (local runtime)
267
+ # -----------------------------------------------------------------------------------------------
268
+ def cmd_agentcore(args):
269
+ """Run or invoke a local Bedrock AgentCore agent."""
270
+ from oblako.engines import agentcore
271
+
272
+ if args.action == "run":
273
+ agentcore.run(args.target, port=args.port)
274
+ else: # invoke
275
+ import json
276
+
277
+ payload = json.loads(args.target) if args.target else {}
278
+ print(json.dumps(agentcore.invoke(payload, port=args.port), indent=2))
279
+
280
+
281
+ # -----------------------------------------------------------------------------------------------
282
+ # Test Commands
283
+ # -----------------------------------------------------------------------------------------------
284
+ def cmd_test(args):
285
+ """Run unit tests."""
286
+ import subprocess
287
+
288
+ cmd = [
289
+ sys.executable,
290
+ "-m",
291
+ "pytest",
292
+ "tests/test_bedrock_adapter.py",
293
+ "tests/test_services.py",
294
+ "-v",
295
+ ]
296
+ sys.exit(subprocess.call(cmd))
297
+
298
+
299
+ def cmd_test_integration(args):
300
+ """Run integration tests."""
301
+ import subprocess
302
+
303
+ cmd = [
304
+ sys.executable,
305
+ "-m",
306
+ "pytest",
307
+ "tests/",
308
+ "-v",
309
+ "--ignore=tests/test_bedrock_adapter.py",
310
+ "--ignore=tests/test_services.py",
311
+ ]
312
+ sys.exit(subprocess.call(cmd))
313
+
314
+
315
+ def cmd_configure(args):
316
+ """Write an AWS profile whose endpoints are oblako's (see oblako.profile)."""
317
+ from oblako.profile import ProfileDowngradeError, write_profile
318
+
319
+ try:
320
+ out = write_profile(args.profile, force=args.force)
321
+ except ProfileDowngradeError as e:
322
+ print(f"error: {e}", file=sys.stderr)
323
+ sys.exit(1)
324
+ print(
325
+ f"profile {out['profile']}: {out['services']} service endpoints in "
326
+ f"{out['config']}, keys in {out['credentials']}"
327
+ )
328
+ print(f"Use it with: export AWS_PROFILE={out['profile']}")
329
+
330
+
331
+ def cmd_trust(args):
332
+ """Trust redshift-local's TLS cert in a venv's redshift-connector bundle."""
333
+ import subprocess
334
+
335
+ from oblako.services import RedshiftService
336
+
337
+ service = RedshiftService()
338
+ try:
339
+ if args.remove:
340
+ print(service.untrust(python_exe=args.python))
341
+ return
342
+ print(service.trust_cert(python_exe=args.python))
343
+ # no cert found, no redshift_connector in that interpreter, or an unwritable
344
+ # bundle: surface a clear message, not a trace
345
+ except (RuntimeError, OSError, subprocess.CalledProcessError) as e:
346
+ print(f"error: {e}", file=sys.stderr)
347
+ sys.exit(1)
348
+ print(
349
+ "Now use sslmode=verify-ca: dbt profile `sslmode: verify-ca`, or "
350
+ "redshift_connector.connect(..., ssl=True, sslmode='verify-ca')."
351
+ )
352
+
353
+
354
+ # -----------------------------------------------------------------------------------------------
355
+ # Helpers
356
+ # -----------------------------------------------------------------------------------------------
357
+ def _get_service(oblako: Oblako, name: str):
358
+ services = {
359
+ "bedrock": oblako.bedrock,
360
+ "ollama": oblako.bedrock, # alias (engine)
361
+ "opensearch": oblako.opensearch,
362
+ "redshift": oblako.redshift,
363
+ "rds": oblako.rds,
364
+ "aurora": oblako.rds, # alias (shares the rds engine + control plane)
365
+ "moto": oblako.moto,
366
+ "s3": oblako.s3,
367
+ "dynamodb": oblako.dynamodb,
368
+ "kinesis": oblako.kinesis,
369
+ "stepfunctions": oblako.stepfunctions,
370
+ "iceberg": oblako.iceberg, # the Iceberg REST catalog (S3 Tables, Glue)
371
+ "trino": oblako.trino, # Athena's engine; starts the Glue engine too
372
+ }
373
+ if name not in services:
374
+ print(f"Unknown service: {name}")
375
+ print(f"Available: {', '.join([*services, *host.ENGINES])}")
376
+ sys.exit(1)
377
+ return services[name]
378
+
379
+
380
+ # -----------------------------------------------------------------------------------------------
381
+ # Entry Point
382
+ # -----------------------------------------------------------------------------------------------
383
+ def build_parser() -> argparse.ArgumentParser:
384
+ """Build the command-line parser, with every subcommand and its options."""
385
+ parser = argparse.ArgumentParser(prog="oblako", description="Local AWS platform")
386
+ sub = parser.add_subparsers(dest="command")
387
+
388
+ p_up = sub.add_parser("up", help="Start services")
389
+ p_up.add_argument("service", nargs="?", help="Start a specific service")
390
+ p_up.add_argument(
391
+ "--timeout",
392
+ type=float,
393
+ default=120.0,
394
+ help="Seconds to wait for each service to become ready (default: 120)",
395
+ )
396
+ p_up.set_defaults(func=cmd_up)
397
+
398
+ p_down = sub.add_parser("down", help="Stop services")
399
+ p_down.add_argument("service", nargs="?", help="Stop a specific service")
400
+ p_down.set_defaults(func=cmd_down)
401
+
402
+ p_status = sub.add_parser("status", help="Show service status")
403
+ p_status.set_defaults(func=cmd_status)
404
+
405
+ p_logs = sub.add_parser("logs", help="Show service logs")
406
+ p_logs.add_argument("service", help="Service name")
407
+ p_logs.add_argument("-n", "--tail", type=int, default=50, help="Number of lines")
408
+ p_logs.set_defaults(func=cmd_logs)
409
+
410
+ p_pull = sub.add_parser("pull", help="Pull an Ollama model")
411
+ p_pull.add_argument("model", nargs="?", help="Model name (default: qwen2.5:0.5b)")
412
+ p_pull.set_defaults(func=cmd_pull)
413
+
414
+ p_models = sub.add_parser("models", help="List Ollama models")
415
+ p_models.set_defaults(func=cmd_models)
416
+
417
+ p_test = sub.add_parser("test", help="Run unit tests")
418
+ p_test.set_defaults(func=cmd_test)
419
+
420
+ p_ti = sub.add_parser("test-integration", help="Run integration tests")
421
+ p_ti.set_defaults(func=cmd_test_integration)
422
+
423
+ p_dash = sub.add_parser("dashboard", help="Start the web dashboard")
424
+ p_dash.add_argument(
425
+ "-p", "--port", type=int, default=None, help="Port (default: 8000)"
426
+ )
427
+ p_dash.add_argument(
428
+ "--host",
429
+ default="127.0.0.1",
430
+ help="Address to listen on (default: 127.0.0.1, this machine only; "
431
+ "the dashboard has no login)",
432
+ )
433
+ p_dash.set_defaults(func=cmd_dashboard)
434
+
435
+ p_nb = sub.add_parser(
436
+ "notebook", help="Launch JupyterLab wired to oblako's services"
437
+ )
438
+ p_nb.add_argument(
439
+ "-p", "--port", type=int, default=8888, help="Port (default: 8888)"
440
+ )
441
+ p_nb.add_argument(
442
+ "--dir", help="Notebook workspace dir (default: ~/.oblako/notebooks)"
443
+ )
444
+ p_nb.set_defaults(func=cmd_notebook)
445
+
446
+ p_rsd = sub.add_parser("redshift-data", help="Run the Redshift Data API server")
447
+ p_rsd.add_argument(
448
+ "-p", "--port", type=int, default=8002, help="Port (default: 8002)"
449
+ )
450
+ p_rsd.set_defaults(func=cmd_redshift_data)
451
+
452
+ p_configure = sub.add_parser(
453
+ "configure",
454
+ help="Write an AWS profile (default: oblako) that points the SDKs and CLI at oblako",
455
+ )
456
+ p_configure.add_argument(
457
+ "--profile", default="oblako", help="Profile name (default: oblako)"
458
+ )
459
+ p_configure.add_argument(
460
+ "--force",
461
+ action="store_true",
462
+ help="Write the profile even if it drops settings a newer oblako wrote",
463
+ )
464
+ p_configure.set_defaults(func=cmd_configure)
465
+
466
+ p_trust = sub.add_parser(
467
+ "trust",
468
+ help="Trust redshift-local's TLS cert so redshift-connector/dbt can use sslmode=verify-ca",
469
+ )
470
+ p_trust.add_argument(
471
+ "--python",
472
+ help="Interpreter of the venv to patch (default: the current one)",
473
+ )
474
+ p_trust.add_argument(
475
+ "--remove",
476
+ action="store_true",
477
+ help="Stop trusting: remove oblako's certificates and the keeper from the venv",
478
+ )
479
+ p_trust.set_defaults(func=cmd_trust)
480
+
481
+ p_brt = sub.add_parser("bedrock-runtime", help="Run the Bedrock Runtime server")
482
+ p_brt.add_argument(
483
+ "-p", "--port", type=int, default=8004, help="Port (default: 8004)"
484
+ )
485
+ p_brt.set_defaults(func=cmd_bedrock_runtime)
486
+
487
+ p_rd = sub.add_parser("rds-data", help="Run the RDS Data API server")
488
+ p_rd.add_argument(
489
+ "-p", "--port", type=int, default=8006, help="Port (default: 8006)"
490
+ )
491
+ p_rd.set_defaults(func=cmd_rds_data)
492
+
493
+ p_cfn = sub.add_parser("cloudformation", help="Run the CloudFormation server")
494
+ p_cfn.add_argument(
495
+ "-p",
496
+ "--port",
497
+ type=int,
498
+ default=ports.CLOUDFORMATION,
499
+ help="Port (default: 8017)",
500
+ )
501
+ p_cfn.set_defaults(func=cmd_cloudformation)
502
+
503
+ p_ac = sub.add_parser("agentcore", help="Run or invoke a local AgentCore agent")
504
+ p_ac.add_argument(
505
+ "action",
506
+ choices=["run", "invoke"],
507
+ help="run an agent file, or invoke a running one",
508
+ )
509
+ p_ac.add_argument("target", help="agent .py file (run) or JSON payload (invoke)")
510
+ p_ac.add_argument(
511
+ "-p", "--port", type=int, default=8080, help="Port (default: 8080)"
512
+ )
513
+ p_ac.set_defaults(func=cmd_agentcore)
514
+
515
+ return parser
516
+
517
+
518
+ def main():
519
+ """Parse arguments and dispatch to the appropriate command handler."""
520
+ parser = build_parser()
521
+ args = parser.parse_args()
522
+ if not args.command:
523
+ banner.show()
524
+ parser.print_help()
525
+ sys.exit(1)
526
+ args.func(args)
527
+
528
+
529
+ if __name__ == "__main__":
530
+ main()
oblako/config.py ADDED
@@ -0,0 +1,68 @@
1
+ """Central region/account configuration for oblako clients and ARNs.
2
+
3
+ oblako is single-backend, so the account is identity metadata, not isolated
4
+ storage — but ARNs, IAM, and STS should reflect a consistent account + region.
5
+ Both are read from the environment so a user can pick a region/account and have
6
+ every boto3 client oblako hands out use it:
7
+
8
+ OBLAKO_REGION=eu-west-1 OBLAKO_ACCOUNT_ID=222222222222 oblako up
9
+
10
+ 123456789012 is moto's default account, so IAM/STS ARNs line up with it.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import os
16
+
17
+ DEFAULT_REGION = "us-east-1"
18
+ DEFAULT_ACCOUNT_ID = "123456789012"
19
+
20
+ # Common AWS regions offered in the dashboard region picker.
21
+ REGIONS = [
22
+ "us-east-1",
23
+ "us-east-2",
24
+ "us-west-1",
25
+ "us-west-2",
26
+ "eu-west-1",
27
+ "eu-west-2",
28
+ "eu-central-1",
29
+ "ap-south-1",
30
+ "ap-southeast-1",
31
+ "ap-southeast-2",
32
+ "ap-northeast-1",
33
+ "sa-east-1",
34
+ "ca-central-1",
35
+ ]
36
+
37
+ # Runtime overrides (set by the dashboard region picker); take precedence over env.
38
+ _region_override: str | None = None
39
+ _account_override: str | None = None
40
+
41
+
42
+ def region() -> str:
43
+ """Return the active AWS region: runtime override, then OBLAKO_REGION, then default."""
44
+ return _region_override or os.environ.get("OBLAKO_REGION") or DEFAULT_REGION
45
+
46
+
47
+ def account_id() -> str:
48
+ """Return the active AWS account id: runtime override, then env, then default."""
49
+ return (
50
+ _account_override or os.environ.get("OBLAKO_ACCOUNT_ID") or DEFAULT_ACCOUNT_ID
51
+ )
52
+
53
+
54
+ def set_region(value: str | None) -> None:
55
+ """Set (or clear, with None) the runtime region override."""
56
+ global _region_override
57
+ _region_override = value or None
58
+
59
+
60
+ def set_account(value: str | None) -> None:
61
+ """Set (or clear, with None) the runtime account override."""
62
+ global _account_override
63
+ _account_override = value or None
64
+
65
+
66
+ def arn(service: str, resource: str, *, region_scoped: bool = True) -> str:
67
+ """Build an ARN for the configured account/region (region blank for global services like IAM)."""
68
+ return f"arn:aws:{service}:{region() if region_scoped else ''}:{account_id()}:{resource}"
@@ -0,0 +1 @@
1
+ """oblako dashboard: FastAPI backend serving the Cloudscape frontend."""