odctl 0.1.0__tar.gz → 0.2.0__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.
Files changed (82) hide show
  1. {odctl-0.1.0 → odctl-0.2.0}/PKG-INFO +1 -1
  2. {odctl-0.1.0 → odctl-0.2.0}/pyproject.toml +1 -1
  3. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/config.py +14 -1
  4. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/main.py +14 -5
  5. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/clickhouse/config.xml +7 -0
  6. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/clickhouse/keeper_config.xml +7 -0
  7. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/compose-analytics.yml +6 -64
  8. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/compose-flink.yml +6 -6
  9. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/compose-kafka.yml +8 -0
  10. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/compose-spark.yml +6 -8
  11. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/deps/fetch_flink_dependencies.sh +7 -1
  12. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/deps/fetch_spark_dependencies.sh +1 -1
  13. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/deps/fetch_standalone_plugins.sh +1 -1
  14. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/docker/spark/Dockerfile +4 -0
  15. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/registry.yml +2 -0
  16. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/spark/spark-defaults.conf +6 -0
  17. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/trino/catalog/clickhouse.properties +1 -1
  18. odctl-0.2.0/src/odctl/resources/trino/catalog/kafka.properties +7 -0
  19. odctl-0.2.0/src/odctl/resources/trino/catalog/kafka_avro.properties +10 -0
  20. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/workspace.py +2 -3
  21. {odctl-0.1.0 → odctl-0.2.0}/tests/test_workspace.py +0 -1
  22. {odctl-0.1.0 → odctl-0.2.0}/uv.lock +1 -1
  23. {odctl-0.1.0 → odctl-0.2.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  24. {odctl-0.1.0 → odctl-0.2.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  25. {odctl-0.1.0 → odctl-0.2.0}/.github/renovate.json +0 -0
  26. {odctl-0.1.0 → odctl-0.2.0}/.github/workflows/build-deps.yml +0 -0
  27. {odctl-0.1.0 → odctl-0.2.0}/.github/workflows/build-platform-images.yml +0 -0
  28. {odctl-0.1.0 → odctl-0.2.0}/.github/workflows/ci.yml +0 -0
  29. {odctl-0.1.0 → odctl-0.2.0}/.github/workflows/e2e-test.yml +0 -0
  30. {odctl-0.1.0 → odctl-0.2.0}/.github/workflows/pipeline.yml +0 -0
  31. {odctl-0.1.0 → odctl-0.2.0}/.github/workflows/publish.yml +0 -0
  32. {odctl-0.1.0 → odctl-0.2.0}/.gitignore +0 -0
  33. {odctl-0.1.0 → odctl-0.2.0}/.pre-commit-config.yaml +0 -0
  34. {odctl-0.1.0 → odctl-0.2.0}/.vscode/settings.json +0 -0
  35. {odctl-0.1.0 → odctl-0.2.0}/LICENSE +0 -0
  36. {odctl-0.1.0 → odctl-0.2.0}/README.md +0 -0
  37. {odctl-0.1.0 → odctl-0.2.0}/image/diagram.png +0 -0
  38. {odctl-0.1.0 → odctl-0.2.0}/image/diagram1.drawio +0 -0
  39. {odctl-0.1.0 → odctl-0.2.0}/image/diagram2.drawio +0 -0
  40. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/__init__.py +0 -0
  41. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/docker.py +0 -0
  42. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/planner.py +0 -0
  43. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/registry.py +0 -0
  44. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/clickhouse/init.sql +0 -0
  45. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/clickhouse/macros-11.xml +0 -0
  46. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/clickhouse/macros-12.xml +0 -0
  47. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/clickhouse/macros-21.xml +0 -0
  48. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/clickhouse/macros-22.xml +0 -0
  49. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/clickhouse/users.xml +0 -0
  50. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/compose-deps.yml +0 -0
  51. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/compose-infra.yml +0 -0
  52. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/compose-metadata.yml +0 -0
  53. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/compose-mlops.yml +0 -0
  54. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/compose-obsv.yml +0 -0
  55. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/compose-orch.yml +0 -0
  56. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/compose-store.yml +0 -0
  57. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/deps/Dockerfile +0 -0
  58. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/deps/build.gradle +0 -0
  59. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/deps/build_connectors.sh +0 -0
  60. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/docker/airflow/Dockerfile +0 -0
  61. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/docker/ray-mlops/Dockerfile +0 -0
  62. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/flink/flink1-conf.yml +0 -0
  63. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/grafana/prometheus.yml +0 -0
  64. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/marquez/marquez.yml +0 -0
  65. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/mlops/serving/lightgbm_inference.py.example +0 -0
  66. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/mlops/serving/vector_search.py.example +0 -0
  67. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/postgres/01-init-databases.sh +0 -0
  68. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/prometheus/alertmanager.yml +0 -0
  69. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/prometheus/prometheus.yml +0 -0
  70. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/seaweed/s3.json +0 -0
  71. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/spark/log4j2.properties +0 -0
  72. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/trino/catalog/iceberg.properties +0 -0
  73. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/trino/catalog/postgres.properties +0 -0
  74. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/resources/trino/catalog/valkey.properties +0 -0
  75. {odctl-0.1.0 → odctl-0.2.0}/src/odctl/ui.py +0 -0
  76. {odctl-0.1.0 → odctl-0.2.0}/tests/conftest.py +0 -0
  77. {odctl-0.1.0 → odctl-0.2.0}/tests/test_cli.py +0 -0
  78. {odctl-0.1.0 → odctl-0.2.0}/tests/test_cli_extended.py +0 -0
  79. {odctl-0.1.0 → odctl-0.2.0}/tests/test_config.py +0 -0
  80. {odctl-0.1.0 → odctl-0.2.0}/tests/test_docker.py +0 -0
  81. {odctl-0.1.0 → odctl-0.2.0}/tests/test_planner.py +0 -0
  82. {odctl-0.1.0 → odctl-0.2.0}/tests/test_registry.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: odctl
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Orchestrator for the Open Data Stack
5
5
  Project-URL: Homepage, https://github.com/jaehyeon-kim/odctl
6
6
  Project-URL: Repository, https://github.com/jaehyeon-kim/odctl
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "odctl"
3
- version = "0.1.0"
3
+ version = "0.2.0"
4
4
  description = "Orchestrator for the Open Data Stack"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -1,5 +1,15 @@
1
1
  import sys
2
2
  from pathlib import Path
3
+ from typing import Optional
4
+
5
+ # Workspace directory requested via the global `--workspace` flag (set in main.py).
6
+ _workspace_override: Optional[Path] = None
7
+
8
+
9
+ def set_workspace_override(path: Optional[Path]) -> None:
10
+ """Record the workspace directory requested via the global `--workspace` flag."""
11
+ global _workspace_override
12
+ _workspace_override = path.resolve() if path is not None else None
3
13
 
4
14
 
5
15
  def get_internal_resources_dir() -> Path:
@@ -25,8 +35,11 @@ def get_workspace_dir() -> Path:
25
35
  Get the path to the local mutable workspace.
26
36
 
27
37
  Returns:
28
- Path: The absolute path to the `.odctl` directory in the current working directory.
38
+ Path: The directory supplied via `--workspace`, or the `.odctl` directory in
39
+ the current working directory when no override is set.
29
40
  """
41
+ if _workspace_override is not None:
42
+ return _workspace_override
30
43
  return Path.cwd() / ".odctl"
31
44
 
32
45
 
@@ -1,9 +1,10 @@
1
+ import logging
1
2
  from pathlib import Path
2
3
  from typing import List, Optional
3
4
 
4
5
  import typer
5
6
 
6
- from odctl import ui
7
+ from odctl import config, ui
7
8
  from odctl.docker import (
8
9
  get_managed_containers,
9
10
  get_managed_logs,
@@ -51,14 +52,20 @@ def main(
51
52
  help="Enable debug-level logging across all commands.",
52
53
  rich_help_panel="Global Options",
53
54
  ),
54
- workspace: Path = typer.Option(
55
- "./.odctl",
55
+ workspace: Optional[Path] = typer.Option(
56
+ None,
56
57
  "--workspace",
57
58
  "-w",
58
- help="Path to the ODCTL workspace directory.",
59
+ help="Path to the ODCTL workspace directory (default: ./.odctl).",
59
60
  rich_help_panel="Global Options",
60
61
  ),
61
62
  ):
63
+ logging.basicConfig(
64
+ level=logging.DEBUG if verbose else logging.WARNING,
65
+ format="%(levelname)s %(name)s: %(message)s",
66
+ force=True,
67
+ )
68
+ config.set_workspace_override(workspace)
62
69
  ctx.obj = {"verbose": verbose, "workspace": workspace}
63
70
 
64
71
 
@@ -183,7 +190,9 @@ def init(
183
190
  "⚠️ This will wipe out local modifications. Are you sure?", abort=True
184
191
  )
185
192
  init_workspace(force=force)
186
- ui.print_success("Workspace initialized! You can now edit any file in ./.odctl/")
193
+ ui.print_success(
194
+ f"Workspace initialized! You can now edit any file in {get_workspace_dir()}/"
195
+ )
187
196
 
188
197
 
189
198
  @app.command(
@@ -9,6 +9,13 @@
9
9
  <background_pool_size>16</background_pool_size>
10
10
  <background_schedule_pool_size>8</background_schedule_pool_size>
11
11
 
12
+ <!-- Docker networks hand out IPv6 addresses, and container names can resolve
13
+ to the AAAA record only, so listening on IPv4 alone leaves peers with
14
+ connection refused. :: is dual-stack and covers IPv4 too, so it comes
15
+ first; the 0.0.0.0 entry is a fallback for hosts without IPv6, where
16
+ the :: bind fails harmlessly thanks to listen_try. -->
17
+ <listen_try>1</listen_try>
18
+ <listen_host>::</listen_host>
12
19
  <listen_host>0.0.0.0</listen_host>
13
20
 
14
21
  <zookeeper>
@@ -4,6 +4,13 @@
4
4
  <level>information</level>
5
5
  <console>1</console>
6
6
  </logger>
7
+ <!-- Docker networks hand out IPv6 addresses, and container names can resolve
8
+ to the AAAA record only, so listening on IPv4 alone leaves peers with
9
+ connection refused. :: is dual-stack and covers IPv4 too, so it comes
10
+ first; the 0.0.0.0 entry is a fallback for hosts without IPv6, where
11
+ the :: bind fails harmlessly thanks to listen_try. -->
12
+ <listen_try>1</listen_try>
13
+ <listen_host>::</listen_host>
7
14
  <listen_host>0.0.0.0</listen_host>
8
15
  <keeper_server>
9
16
  <tcp_port>9181</tcp_port>
@@ -1,7 +1,7 @@
1
1
  name: odctl-analytics
2
2
 
3
3
  x-clickhouse-base: &clickhouse-base
4
- image: clickhouse/clickhouse-server:24.3-alpine
4
+ image: clickhouse/clickhouse-server:25.8-alpine
5
5
  networks:
6
6
  - odctl
7
7
  mem_limit: 4g
@@ -32,7 +32,7 @@ services:
32
32
  # [SHARED] Consensus Node
33
33
  # ==========================================
34
34
  ch-keeper:
35
- image: clickhouse/clickhouse-keeper:24.3-alpine
35
+ image: clickhouse/clickhouse-keeper:25.8-alpine
36
36
  container_name: ch-keeper
37
37
  # Keeper must run in BOTH profiles
38
38
  profiles:
@@ -83,6 +83,7 @@ services:
83
83
  - ./clickhouse/config.xml:/etc/clickhouse-server/config.d/cluster-config.xml
84
84
  - ./clickhouse/users.xml:/etc/clickhouse-server/users.d/custom-users.xml
85
85
  - ./clickhouse/macros-11.xml:/etc/clickhouse-server/config.d/macros.xml
86
+ - ./clickhouse/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
86
87
 
87
88
  ch-12:
88
89
  <<: *clickhouse-base
@@ -95,6 +96,7 @@ services:
95
96
  - ./clickhouse/config.xml:/etc/clickhouse-server/config.d/cluster-config.xml
96
97
  - ./clickhouse/users.xml:/etc/clickhouse-server/users.d/custom-users.xml
97
98
  - ./clickhouse/macros-12.xml:/etc/clickhouse-server/config.d/macros.xml
99
+ - ./clickhouse/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
98
100
 
99
101
  # ==========================================
100
102
  # Shard 2 (Active ONLY in multi profile)
@@ -110,6 +112,7 @@ services:
110
112
  - ./clickhouse/config.xml:/etc/clickhouse-server/config.d/cluster-config.xml
111
113
  - ./clickhouse/users.xml:/etc/clickhouse-server/users.d/custom-users.xml
112
114
  - ./clickhouse/macros-21.xml:/etc/clickhouse-server/config.d/macros.xml
115
+ - ./clickhouse/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
113
116
 
114
117
  ch-22:
115
118
  <<: *clickhouse-base
@@ -122,68 +125,7 @@ services:
122
125
  - ./clickhouse/config.xml:/etc/clickhouse-server/config.d/cluster-config.xml
123
126
  - ./clickhouse/users.xml:/etc/clickhouse-server/users.d/custom-users.xml
124
127
  - ./clickhouse/macros-22.xml:/etc/clickhouse-server/config.d/macros.xml
125
-
126
- # ==========================================
127
- # [INIT: ch-lite] Database Initialization
128
- # ==========================================
129
- ch-init-lite:
130
- image: clickhouse/clickhouse-server:24.3-alpine
131
- container_name: ch-init-lite
132
- profiles: ["ch-lite"]
133
- networks:
134
- - odctl
135
- depends_on:
136
- ch-11:
137
- condition: service_healthy
138
- ch-12:
139
- condition: service_healthy
140
- entrypoint: >
141
- /bin/sh -c "
142
- echo '[ch-lite] Creating databases on Shard 1...' &&
143
- clickhouse-client --host ch-11 --user default --password password --query 'CREATE DATABASE IF NOT EXISTS feature_store;' &&
144
- clickhouse-client --host ch-11 --user default --password password --query 'CREATE DATABASE IF NOT EXISTS odctl;' &&
145
- clickhouse-client --host ch-12 --user default --password password --query 'CREATE DATABASE IF NOT EXISTS feature_store;' &&
146
- clickhouse-client --host ch-12 --user default --password password --query 'CREATE DATABASE IF NOT EXISTS odctl;' &&
147
- echo '✅ [ch-lite] Databases created successfully!' &&
148
- sleep infinity
149
- "
150
-
151
- # ==========================================
152
- # [INIT: ch-full] Database Initialization
153
- # ==========================================
154
- ch-init-full:
155
- image: clickhouse/clickhouse-server:24.3-alpine
156
- container_name: ch-init-full
157
- profiles: ["ch-full"]
158
- networks:
159
- - odctl
160
- depends_on:
161
- ch-11:
162
- condition: service_healthy
163
- ch-12:
164
- condition: service_healthy
165
- ch-21:
166
- condition: service_healthy
167
- ch-22:
168
- condition: service_healthy
169
- entrypoint: >
170
- /bin/sh -c "
171
- echo '[ch-full] Creating databases on Shard 1...' &&
172
- clickhouse-client --host ch-11 --user default --password password --query 'CREATE DATABASE IF NOT EXISTS feature_store;' &&
173
- clickhouse-client --host ch-11 --user default --password password --query 'CREATE DATABASE IF NOT EXISTS odctl;' &&
174
- clickhouse-client --host ch-12 --user default --password password --query 'CREATE DATABASE IF NOT EXISTS feature_store;' &&
175
- clickhouse-client --host ch-12 --user default --password password --query 'CREATE DATABASE IF NOT EXISTS odctl;' &&
176
-
177
- echo '[ch-full] Creating databases on Shard 2...' &&
178
- clickhouse-client --host ch-21 --user default --password password --query 'CREATE DATABASE IF NOT EXISTS feature_store;' &&
179
- clickhouse-client --host ch-21 --user default --password password --query 'CREATE DATABASE IF NOT EXISTS odctl;' &&
180
- clickhouse-client --host ch-22 --user default --password password --query 'CREATE DATABASE IF NOT EXISTS feature_store;' &&
181
- clickhouse-client --host ch-22 --user default --password password --query 'CREATE DATABASE IF NOT EXISTS odctl;' &&
182
-
183
- echo '✅ [ch-full] All databases created across the cluster!' &&
184
- sleep infinity
185
- "
186
-
128
+ - ./clickhouse/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
187
129
 
188
130
  # ==========================================
189
131
  # [PROFILE: trino] Federated SQL Engine
@@ -9,18 +9,18 @@ x-flink-common: &flink-common
9
9
 
10
10
  x-flink1-base: &flink1-base
11
11
  <<: *flink-common
12
- image: flink:1.20.4-java17
12
+ image: flink:1.20.5-java17
13
13
  environment:
14
14
  TZ: ${TZ:-UTC}
15
15
  entrypoint:
16
16
  - /bin/bash
17
17
  - -c
18
18
  - |
19
- # Load dependencies
20
- mkdir -p /opt/flink/usrlib
21
- cp /mnt/shared-deps/shared/*.jar /opt/flink/usrlib/ 2>/dev/null || true
22
- cp /mnt/shared-deps/flink/1.x/*.jar /opt/flink/usrlib/ 2>/dev/null || true
23
- cp /mnt/shared-deps/connect/confluent-converters/*.jar /opt/flink/usrlib/ 2>/dev/null || true
19
+ # Load dependencies into lib/, not usrlib/. Only lib/ reaches the system
20
+ # classloader, which is where SQL connector factories are looked up.
21
+ cp /mnt/shared-deps/shared/*.jar /opt/flink/lib/ 2>/dev/null || true
22
+ cp /mnt/shared-deps/flink/1.x/*.jar /opt/flink/lib/ 2>/dev/null || true
23
+ cp /mnt/shared-deps/connect/confluent-converters/*.jar /opt/flink/lib/ 2>/dev/null || true
24
24
 
25
25
  # S3 Plugin Linking
26
26
  echo "Linking S3 FileSystem Plugin..."
@@ -39,6 +39,14 @@ services:
39
39
  KAFKA_CONTROLLER_QUORUM_VOTERS: "1@kafka:19091"
40
40
  KAFKA_NUM_PARTITIONS: "3"
41
41
  KAFKA_DEFAULT_REPLICATION_FACTOR: "1"
42
+ # Internal topics default to a replication factor of 3, which a single
43
+ # broker can never satisfy. Without these, __consumer_offsets is never
44
+ # created and every consumer group waits on the coordinator forever.
45
+ KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: "1"
46
+ KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: "1"
47
+ KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: "1"
48
+ KAFKA_SHARE_COORDINATOR_STATE_TOPIC_REPLICATION_FACTOR: "1"
49
+ KAFKA_SHARE_COORDINATOR_STATE_TOPIC_MIN_ISR: "1"
42
50
  <<: *kafka_broker_common_env_vars
43
51
  mem_limit: 1500m
44
52
 
@@ -12,6 +12,12 @@ x-spark-base: &spark-base
12
12
  environment:
13
13
  TZ: ${TZ:-UTC}
14
14
  SPARK_DIST_CLASSPATH: "/mnt/shared-deps/shared/*:/mnt/shared-deps/spark/*"
15
+ # Picked up by boto3 and by any AWS SDK client that falls back to the
16
+ # default credential chain
17
+ AWS_REGION: us-east-1
18
+ AWS_ACCESS_KEY_ID: user
19
+ AWS_SECRET_ACCESS_KEY: password
20
+ SPARK_WORKER_MEMORY: "1500m"
15
21
 
16
22
  services:
17
23
  spark-master:
@@ -23,8 +29,6 @@ services:
23
29
  - "9080:8080"
24
30
  - "7077:7077"
25
31
  mem_limit: 1g
26
- environment:
27
- SPARK_WORKER_MEMORY: "1500m"
28
32
  healthcheck:
29
33
  test: ["CMD", "curl", "-f", "http://localhost:8080"]
30
34
  interval: 10s
@@ -39,8 +43,6 @@ services:
39
43
  ports:
40
44
  - "9081:8081"
41
45
  mem_limit: 2g
42
- environment:
43
- SPARK_WORKER_MEMORY: "1500m"
44
46
  depends_on:
45
47
  spark-master:
46
48
  condition: service_healthy
@@ -53,8 +55,6 @@ services:
53
55
  ports:
54
56
  - "9082:8081"
55
57
  mem_limit: 2g
56
- environment:
57
- SPARK_WORKER_MEMORY: "1500m"
58
58
  depends_on:
59
59
  spark-master:
60
60
  condition: service_healthy
@@ -67,8 +67,6 @@ services:
67
67
  ports:
68
68
  - "9083:8081"
69
69
  mem_limit: 2g
70
- environment:
71
- SPARK_WORKER_MEMORY: "1500m"
72
70
  depends_on:
73
71
  spark-master:
74
72
  condition: service_healthy
@@ -10,7 +10,7 @@ fetch_artifact() {
10
10
  else echo " ⬇️ Downloading: $2"; curl -sL -f -o "$1" "$2"; fi
11
11
  }
12
12
  get_maven_version() {
13
- curl -sL "https://repo1.maven.org/maven2/${1}/maven-metadata.xml" | grep -Eo "<version>${2}</version>" | sort -V | tail -1 | sed 's/<\/\?version>//g' || true
13
+ curl -sL "https://repo1.maven.org/maven2/${1}/maven-metadata.xml" | grep -Eo "<version>${2}</version>" | sort -V | tail -1 | sed -E 's#</?version>##g' || true
14
14
  }
15
15
 
16
16
  echo "▶️ Resolving Flink Versions..."
@@ -34,6 +34,12 @@ K_V=$(get_maven_version "org/apache/flink/flink-sql-connector-kafka" "[0-9]+\.[0
34
34
  if [ -z "$K_V" ]; then echo "❌ Error: Kafka connector for Flink ${FLINK_MINOR} is not published yet!"; exit 1; fi
35
35
  fetch_artifact "flink/1.x/kafka.jar" "https://repo1.maven.org/maven2/org/apache/flink/flink-sql-connector-kafka/${K_V}/flink-sql-connector-kafka-${K_V}.jar"
36
36
 
37
+ # Avro with Confluent Schema Registry. This uber JAR is a superset of
38
+ # flink-sql-avro: it registers the plain 'avro' format factory as well as
39
+ # 'avro-confluent' and 'debezium-avro-confluent', so adding both would duplicate
40
+ # the Avro classes and register the same factory twice.
41
+ fetch_artifact "flink/1.x/avro-confluent-registry.jar" "https://repo1.maven.org/maven2/org/apache/flink/flink-sql-avro-confluent-registry/${FLINK_V}/flink-sql-avro-confluent-registry-${FLINK_V}.jar"
42
+
37
43
  # JDBC
38
44
  J_V=$(get_maven_version "org/apache/flink/flink-connector-jdbc" "[0-9]+\.[0-9]+\.[0-9]+-${FLINK_MINOR}")
39
45
  if [ -z "$J_V" ]; then echo "❌ Error: JDBC connector for Flink ${FLINK_MINOR} is not published yet!"; exit 1; fi
@@ -10,7 +10,7 @@ fetch_artifact() {
10
10
  else echo " ⬇️ Downloading: $2"; curl -sL -f -o "$1" "$2"; fi
11
11
  }
12
12
  get_maven_version() {
13
- curl -sL "https://repo1.maven.org/maven2/${1}/maven-metadata.xml" | grep -Eo "<version>${2}</version>" | sort -V | tail -1 | sed 's/<\/\?version>//g' || true
13
+ curl -sL "https://repo1.maven.org/maven2/${1}/maven-metadata.xml" | grep -Eo "<version>${2}</version>" | sort -V | tail -1 | sed -E 's#</?version>##g' || true
14
14
  }
15
15
 
16
16
  echo "▶️ Resolving Spark Versions..."
@@ -10,7 +10,7 @@ fetch_artifact() {
10
10
  else echo " ⬇️ Downloading: $2"; curl -sL -f -o "$1" "$2"; fi
11
11
  }
12
12
  get_maven_version() {
13
- curl -sL "https://repo1.maven.org/maven2/${1}/maven-metadata.xml" | grep -Eo "<version>${2}</version>" | sort -V | tail -1 | sed 's/<\/\?version>//g' || true
13
+ curl -sL "https://repo1.maven.org/maven2/${1}/maven-metadata.xml" | grep -Eo "<version>${2}</version>" | sort -V | tail -1 | sed -E 's#</?version>##g' || true
14
14
  }
15
15
 
16
16
  echo "▶️ Resolving Shared & Connector Versions..."
@@ -12,5 +12,9 @@ RUN --mount=type=cache,target=/root/.cache/pip \
12
12
  'mlflow~=3.12.0' \
13
13
  'boto3'
14
14
 
15
+ # Pre-create the event log directory owned by the spark user. A named volume
16
+ # mounted here inherits this ownership, so spark-submit can write its logs.
17
+ RUN mkdir -p /tmp/spark-events && chown 185:185 /tmp/spark-events
18
+
15
19
  # Revert to the official Apache Spark non-root user ID
16
20
  USER 185
@@ -202,6 +202,8 @@ stacks:
202
202
  • Trino UI & JDBC connection (user: user, password: none):
203
203
  * External (Localhost): http://localhost:8080
204
204
  * Internal (Docker): http://trino:8080
205
+ • Catalogs: iceberg, kafka (JSON topics), kafka_avro (Avro via
206
+ the schema registry), clickhouse, postgres, valkey
205
207
  metabase: |
206
208
  • Metabase Dashboard Engine:
207
209
  * External (Localhost): http://localhost:3000
@@ -17,6 +17,12 @@ spark.sql.catalog.iceberg.s3.endpoint http://seaweed:8333
17
17
  spark.sql.catalog.iceberg.s3.path-style-access true
18
18
  spark.sql.defaultCatalog iceberg
19
19
 
20
+ # SeaweedFS ignores the region, but the AWS SDK refuses to build a client
21
+ # without one, and the default credential chain finds nothing in a container.
22
+ spark.sql.catalog.iceberg.s3.access-key-id user
23
+ spark.sql.catalog.iceberg.s3.secret-access-key password
24
+ spark.sql.catalog.iceberg.client.region us-east-1
25
+
20
26
  # ========================================
21
27
  # History Server & Event Logging (Local Volume)
22
28
  # ========================================
@@ -1,4 +1,4 @@
1
1
  connector.name=clickhouse
2
- connection-url=jdbc:clickhouse://clickhouse-11:8123/feature_store
2
+ connection-url=jdbc:clickhouse://ch-11:8123/feature_store
3
3
  connection-user=user
4
4
  connection-password=password
@@ -0,0 +1,7 @@
1
+ connector.name=kafka
2
+ kafka.nodes=broker-1:19092
3
+ kafka.default-schema=default
4
+ kafka.table-names=web_events,logs
5
+ # Expose _message, _key and the other underscore columns so raw JSON topics can
6
+ # be read without a table description file
7
+ kafka.hide-internal-columns=false
@@ -0,0 +1,10 @@
1
+ connector.name=kafka
2
+ kafka.nodes=broker-1:19092
3
+ kafka.default-schema=default
4
+ # Tables are discovered from the schema registry, so topics carrying Confluent
5
+ # Avro appear without being listed and their fields arrive typed. This supplier
6
+ # replaces file-based discovery rather than extending it, which is why plain JSON
7
+ # topics live in the separate 'kafka' catalog.
8
+ kafka.table-description-supplier=CONFLUENT
9
+ kafka.confluent-schema-registry-url=http://karapace:8081
10
+ kafka.hide-internal-columns=false
@@ -1,6 +1,5 @@
1
1
  import shutil
2
2
  from importlib.metadata import PackageNotFoundError, version
3
- from pathlib import Path
4
3
 
5
4
  from odctl.config import INTERNAL_RESOURCES_DIR, get_workspace_dir
6
5
 
@@ -34,11 +33,11 @@ def init_workspace(force: bool = False):
34
33
  # Nuke the existing workspace if force is True
35
34
  if force and workspace.exists():
36
35
  shutil.rmtree(workspace)
37
- print(f"🗑️ Removed existing workspace at {workspace.relative_to(Path.cwd())}/")
36
+ print(f"🗑️ Removed existing workspace at {workspace}/")
38
37
 
39
38
  if not workspace.exists():
40
39
  workspace.mkdir(parents=True)
41
- print(f"📁 Created workspace at {workspace.relative_to(Path.cwd())}/")
40
+ print(f"📁 Created workspace at {workspace}/")
42
41
 
43
42
  # Iterate over everything in the internal resources
44
43
  for item in INTERNAL_RESOURCES_DIR.iterdir():
@@ -10,7 +10,6 @@ def test_init_workspace(tmp_path, monkeypatch):
10
10
  monkeypatch.setattr(
11
11
  "odctl.workspace.get_workspace_dir", lambda: tmp_path / ".odctl"
12
12
  )
13
- monkeypatch.setattr("odctl.workspace.Path.cwd", lambda: tmp_path)
14
13
 
15
14
  # Mock INTERNAL_RESOURCES_DIR to point to a temporary internal dir
16
15
  internal_dir = tmp_path / "internal"
@@ -387,7 +387,7 @@ wheels = [
387
387
 
388
388
  [[package]]
389
389
  name = "odctl"
390
- version = "0.0.10"
390
+ version = "0.1.1"
391
391
  source = { editable = "." }
392
392
  dependencies = [
393
393
  { name = "pydantic" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes