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.
- oblako/__init__.py +3 -0
- oblako/banner.py +64 -0
- oblako/cli.py +530 -0
- oblako/config.py +68 -0
- oblako/dashboard/__init__.py +1 -0
- oblako/dashboard/api.py +1912 -0
- oblako/dashboard/frontend/.gitignore +25 -0
- oblako/dashboard/frontend/README.md +16 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- oblako/dashboard/frontend/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- oblako/dashboard/frontend/dist/assets/duckdb-browser-DQUTRvrj.js +2 -0
- oblako/dashboard/frontend/dist/assets/index-CIhx4uB3.js +401 -0
- oblako/dashboard/frontend/dist/assets/index-CLqonV80.css +1 -0
- oblako/dashboard/frontend/dist/favicon.svg +1 -0
- oblako/dashboard/frontend/dist/icons.svg +24 -0
- oblako/dashboard/frontend/dist/index.html +14 -0
- oblako/dashboard/frontend/public/favicon.svg +1 -0
- oblako/dashboard/frontend/public/icons.svg +24 -0
- oblako/engines/__init__.py +4 -0
- oblako/engines/agentcore/__init__.py +108 -0
- oblako/engines/appconfig/__init__.py +85 -0
- oblako/engines/appconfig/agent.py +176 -0
- oblako/engines/appconfig/app.py +280 -0
- oblako/engines/appconfig/engine.py +302 -0
- oblako/engines/appconfig/rule_evaluator.py +305 -0
- oblako/engines/athena/__init__.py +85 -0
- oblako/engines/athena/app.py +394 -0
- oblako/engines/athena/catalogs.py +44 -0
- oblako/engines/athena/ctas.py +131 -0
- oblako/engines/athena/workgroups.py +167 -0
- oblako/engines/bedrock/__init__.py +1 -0
- oblako/engines/bedrock/adapter.py +299 -0
- oblako/engines/bedrock/backends.py +211 -0
- oblako/engines/bedrock/eventstream.py +45 -0
- oblako/engines/bedrock/foundation_models.py +190 -0
- oblako/engines/bedrock/guardrails.py +174 -0
- oblako/engines/bedrock/jobs.py +197 -0
- oblako/engines/bedrock/models.py +142 -0
- oblako/engines/bedrock/ollama_client.py +75 -0
- oblako/engines/bedrock_runtime/__init__.py +63 -0
- oblako/engines/bedrock_runtime/app.py +388 -0
- oblako/engines/cloudformation/__init__.py +80 -0
- oblako/engines/cloudformation/app.py +361 -0
- oblako/engines/cloudformation/engine.py +601 -0
- oblako/engines/cloudformation/providers.py +645 -0
- oblako/engines/cloudformation/transform.py +113 -0
- oblako/engines/dynamodb_vectors/__init__.py +92 -0
- oblako/engines/dynamodb_vectors/app.py +685 -0
- oblako/engines/ecs_control/__init__.py +76 -0
- oblako/engines/ecs_control/app.py +136 -0
- oblako/engines/ecs_metadata/__init__.py +149 -0
- oblako/engines/eventbridge/__init__.py +84 -0
- oblako/engines/eventbridge/app.py +503 -0
- oblako/engines/firehose/__init__.py +90 -0
- oblako/engines/firehose/app.py +829 -0
- oblako/engines/glue_catalog/__init__.py +788 -0
- oblako/engines/glue_catalog/crawlers.py +942 -0
- oblako/engines/glue_catalog/expression.py +182 -0
- oblako/engines/glue_catalog/jobs.py +297 -0
- oblako/engines/glue_catalog/store.py +287 -0
- oblako/engines/glue_catalog/workflows.py +647 -0
- oblako/engines/host.py +133 -0
- oblako/engines/iam/__init__.py +5 -0
- oblako/engines/iam/evaluator.py +98 -0
- oblako/engines/identity.py +68 -0
- oblako/engines/lambda_shim/__init__.py +142 -0
- oblako/engines/mlflow/Dockerfile +27 -0
- oblako/engines/mlflow/__init__.py +9 -0
- oblako/engines/mwaa/__init__.py +81 -0
- oblako/engines/mwaa/app.py +176 -0
- oblako/engines/mwaa/environments.py +694 -0
- oblako/engines/rds_control/__init__.py +85 -0
- oblako/engines/rds_control/app.py +277 -0
- oblako/engines/rds_control/instances.py +409 -0
- oblako/engines/rds_data/__init__.py +67 -0
- oblako/engines/rds_data/app.py +147 -0
- oblako/engines/rds_data/executor.py +288 -0
- oblako/engines/redshift_control/__init__.py +85 -0
- oblako/engines/redshift_control/app.py +269 -0
- oblako/engines/redshift_control/clusters.py +356 -0
- oblako/engines/redshift_control/credentials.py +135 -0
- oblako/engines/redshift_control/serverless.py +377 -0
- oblako/engines/redshift_data/__init__.py +71 -0
- oblako/engines/redshift_data/app.py +264 -0
- oblako/engines/redshift_data/executor.py +373 -0
- oblako/engines/redshift_ml/__init__.py +85 -0
- oblako/engines/s3_ext/__init__.py +738 -0
- oblako/engines/s3_ext/inventory.py +218 -0
- oblako/engines/s3_ext/notifications.py +308 -0
- oblako/engines/s3tables/__init__.py +485 -0
- oblako/engines/s3tables/iceberg.py +185 -0
- oblako/engines/s3vectors/__init__.py +447 -0
- oblako/engines/sagemaker/__init__.py +74 -0
- oblako/engines/sagemaker/app.py +649 -0
- oblako/engines/sagemaker/executor.py +1997 -0
- oblako/engines/sagemaker/stubs.py +113 -0
- oblako/images/notebook/Dockerfile +23 -0
- oblako/images/notebook/aws-config +6 -0
- oblako/images/redshift/Dockerfile +91 -0
- oblako/images/redshift/entrypoint.sh +84 -0
- oblako/images/redshift/ext/Makefile +4 -0
- oblako/images/redshift/ext/oblako_redshift.c +80 -0
- oblako/images/redshift/initdb.d/00_extensions.sql +14 -0
- oblako/images/redshift/initdb.d/01_system_tables.sql +32 -0
- oblako/images/redshift/initdb.d/02_functions.sql +56 -0
- oblako/images/redshift/initdb.d/03_date_functions.sql +137 -0
- oblako/images/redshift/initdb.d/04_template1_date_functions.sql +5 -0
- oblako/images/redshift/initdb.d/05_catalog_views.sql +207 -0
- oblako/images/redshift/initdb.d/06_propagate_catalog_views.sql +16 -0
- oblako/images/redshift/initdb.d/07_copy_unload.sql +41 -0
- oblako/images/redshift/initdb.d/08_super.sql +72 -0
- oblako/images/redshift/initdb.d/09_redshift_ml.sql +97 -0
- oblako/images/redshift/initdb.d/10_propagate_redshift_ml.sql +7 -0
- oblako/images/redshift/initdb.d/11_integer_avg.sql +117 -0
- oblako/images/redshift/initdb.d/12_password_auth.sh +11 -0
- oblako/images/redshift/initdb.d/13_iceberg.sql +271 -0
- oblako/images/redshift/initdb.d/99_system_catalog.sql +172 -0
- oblako/images/redshift/ml_train/Dockerfile +10 -0
- oblako/images/redshift/ml_train/train.py +300 -0
- oblako/images/redshift/proxy/copy_unload.py +895 -0
- oblako/images/redshift/proxy/datepart.py +76 -0
- oblako/images/redshift/proxy/iceberg_tables.py +1477 -0
- oblako/images/redshift/proxy/integer_avg.py +86 -0
- oblako/images/redshift/proxy/listagg.py +130 -0
- oblako/images/redshift/proxy/pivot_unpivot.py +127 -0
- oblako/images/redshift/proxy/redshift_ml.py +966 -0
- oblako/images/redshift/proxy/redshift_proxy.py +800 -0
- oblako/images/redshift/proxy/super_nav.py +329 -0
- oblako/images/redshift-cluster/000_citus.sql +3 -0
- oblako/images/redshift-cluster/13_svv_table_info.sql +80 -0
- oblako/images/redshift-cluster/Dockerfile +55 -0
- oblako/images/redshift-cluster/cluster-entrypoint.sh +46 -0
- oblako/notebook.py +299 -0
- oblako/ports.py +89 -0
- oblako/profile.py +160 -0
- oblako/services/__init__.py +39 -0
- oblako/services/appconfig.py +62 -0
- oblako/services/awslambda.py +106 -0
- oblako/services/backends.py +730 -0
- oblako/services/base.py +145 -0
- oblako/services/bedrock.py +139 -0
- oblako/services/boto.py +79 -0
- oblako/services/caddy.py +102 -0
- oblako/services/cloudformation.py +50 -0
- oblako/services/dynamodb.py +67 -0
- oblako/services/ec2.py +210 -0
- oblako/services/ecs.py +640 -0
- oblako/services/elbv2.py +256 -0
- oblako/services/glue.py +260 -0
- oblako/services/glue_assets/S3ProxyCompatInterceptor.java +89 -0
- oblako/services/glue_catalog.py +40 -0
- oblako/services/iam.py +130 -0
- oblako/services/iceberg.py +108 -0
- oblako/services/kinesis.py +55 -0
- oblako/services/mlflow.py +170 -0
- oblako/services/moto.py +76 -0
- oblako/services/opensearch.py +72 -0
- oblako/services/platform.py +150 -0
- oblako/services/rds.py +240 -0
- oblako/services/redshift.py +502 -0
- oblako/services/s3proxy.py +267 -0
- oblako/services/sagemaker.py +460 -0
- oblako/services/sfn_templates.py +454 -0
- oblako/services/stepfunctions.py +102 -0
- oblako/services/trino.py +156 -0
- oblako-0.1.0.dist-info/METADATA +205 -0
- oblako-0.1.0.dist-info/RECORD +228 -0
- oblako-0.1.0.dist-info/WHEEL +4 -0
- oblako-0.1.0.dist-info/entry_points.txt +2 -0
- oblako-0.1.0.dist-info/licenses/LICENSE +202 -0
- oblako-0.1.0.dist-info/licenses/NOTICE +11 -0
oblako/__init__.py
ADDED
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."""
|