odctl 0.3.0__tar.gz → 0.3.1__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.
- odctl-0.3.1/.github/scripts/smoke.sh +196 -0
- {odctl-0.3.0 → odctl-0.3.1}/.github/workflows/e2e-test.yml +33 -27
- odctl-0.3.1/.github/workflows/pipeline.yml +69 -0
- odctl-0.3.1/.github/workflows/publish.yml +90 -0
- {odctl-0.3.0 → odctl-0.3.1}/PKG-INFO +1 -1
- {odctl-0.3.0 → odctl-0.3.1}/pyproject.toml +1 -1
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/docker.py +6 -2
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/compose-infra.yml +2 -2
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/compose-kafka.yml +5 -5
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/compose-metadata.yml +2 -2
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/compose-store.yml +2 -2
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/deps/Dockerfile +7 -3
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/deps/build.gradle +4 -2
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/deps/fetch_flink_dependencies.sh +5 -6
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/deps/fetch_spark_dependencies.sh +6 -2
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/deps/fetch_standalone_plugins.sh +6 -2
- odctl-0.3.1/src/odctl/resources/deps/versions.env +19 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/registry.yml +1 -1
- {odctl-0.3.0 → odctl-0.3.1}/tests/test_docker.py +3 -1
- odctl-0.3.0/.github/workflows/pipeline.yml +0 -34
- odctl-0.3.0/.github/workflows/publish.yml +0 -34
- {odctl-0.3.0 → odctl-0.3.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/.github/renovate.json +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/.github/workflows/build-deps.yml +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/.github/workflows/build-platform-images.yml +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/.github/workflows/ci.yml +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/.gitignore +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/.pre-commit-config.yaml +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/.vscode/settings.json +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/LICENSE +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/README.md +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/image/diagram.png +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/image/diagram1.drawio +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/image/diagram2.drawio +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/__init__.py +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/config.py +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/main.py +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/planner.py +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/registry.py +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/clickhouse/config.xml +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/clickhouse/init.sql +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/clickhouse/keeper_config.xml +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/clickhouse/macros-11.xml +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/clickhouse/macros-12.xml +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/clickhouse/macros-21.xml +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/clickhouse/macros-22.xml +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/clickhouse/users.xml +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/compose-analytics.yml +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/compose-deps.yml +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/compose-flink.yml +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/compose-mlops.yml +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/compose-obsv.yml +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/compose-orch.yml +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/compose-spark.yml +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/deps/build_connectors.sh +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/docker/airflow/Dockerfile +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/docker/ray-mlops/Dockerfile +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/docker/spark/Dockerfile +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/flink/flink-conf.yml +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/grafana/prometheus.yml +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/marquez/marquez.yml +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/mlops/serving/lightgbm_inference.py.example +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/mlops/serving/vector_search.py.example +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/postgres/01-init-databases.sh +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/prometheus/alertmanager.yml +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/prometheus/prometheus.yml +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/seaweed/s3.json +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/spark/log4j2.properties +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/spark/spark-defaults.conf +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/trino/catalog/clickhouse.properties +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/trino/catalog/iceberg.properties +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/trino/catalog/kafka.properties +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/trino/catalog/postgres.properties +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/resources/trino/catalog/valkey.properties +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/ui.py +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/src/odctl/workspace.py +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/tests/conftest.py +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/tests/test_cli.py +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/tests/test_cli_extended.py +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/tests/test_config.py +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/tests/test_planner.py +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/tests/test_registry.py +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/tests/test_workspace.py +0 -0
- {odctl-0.3.0 → odctl-0.3.1}/uv.lock +0 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Functional smoke test for one odctl profile.
|
|
3
|
+
#
|
|
4
|
+
# Liveness is not enough: every bug fixed in 0.2.x and 0.3.0 left its containers
|
|
5
|
+
# running and healthy while the profile was unusable. Kafka consumer groups hung
|
|
6
|
+
# forever, Spark could not start a SparkContext, Flink could not find a connector
|
|
7
|
+
# factory, ClickHouse could not consume from the broker, the Trino ClickHouse
|
|
8
|
+
# catalog pointed at a hostname that did not exist, and the Flink Iceberg sink
|
|
9
|
+
# committed nothing while its checkpoints succeeded. Each assertion below exists
|
|
10
|
+
# because something shipped broken past a liveness check.
|
|
11
|
+
#
|
|
12
|
+
# Usage: smoke.sh <profile>
|
|
13
|
+
set -uo pipefail
|
|
14
|
+
|
|
15
|
+
PROFILE="${1:?usage: smoke.sh <profile>}"
|
|
16
|
+
|
|
17
|
+
fail() { echo "❌ $PROFILE: $*"; exit 1; }
|
|
18
|
+
pass() { echo "✅ $PROFILE: $*"; }
|
|
19
|
+
|
|
20
|
+
# Retry a command until it succeeds or the budget runs out.
|
|
21
|
+
retry() {
|
|
22
|
+
local tries="$1" delay="$2"; shift 2
|
|
23
|
+
for ((i = 1; i <= tries; i++)); do
|
|
24
|
+
if "$@" >/dev/null 2>&1; then return 0; fi
|
|
25
|
+
sleep "$delay"
|
|
26
|
+
done
|
|
27
|
+
return 1
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
http_ok() { curl -fsS -o /dev/null --max-time 10 "$1"; }
|
|
31
|
+
|
|
32
|
+
# Some endpoints answer 403 or 404 to an unauthenticated probe, which still
|
|
33
|
+
# proves the service is listening.
|
|
34
|
+
http_reachable() {
|
|
35
|
+
local code
|
|
36
|
+
code=$(curl -s -o /dev/null --max-time 10 -w '%{http_code}' "$1")
|
|
37
|
+
[ -n "$code" ] && [ "$code" != "000" ]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
ch() { docker exec "$1" clickhouse-client --password password -q "$2" 2>&1; }
|
|
41
|
+
|
|
42
|
+
kafka_topic() {
|
|
43
|
+
docker exec kafka /opt/kafka/bin/kafka-topics.sh --bootstrap-server broker-1:19092 "$@"
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
smoke_kafka() {
|
|
47
|
+
local container="$1"
|
|
48
|
+
retry 30 5 docker exec "$container" /opt/kafka/bin/kafka-topics.sh \
|
|
49
|
+
--bootstrap-server broker-1:19092 --list || fail "broker never became reachable"
|
|
50
|
+
docker exec "$container" /opt/kafka/bin/kafka-topics.sh --bootstrap-server broker-1:19092 \
|
|
51
|
+
--create --topic smoke --partitions 1 --replication-factor 1 >/dev/null 2>&1
|
|
52
|
+
for i in 1 2 3 4 5; do echo "{\"n\":$i}"; done | docker exec -i "$container" \
|
|
53
|
+
/opt/kafka/bin/kafka-console-producer.sh --bootstrap-server broker-1:19092 --topic smoke >/dev/null 2>&1 \
|
|
54
|
+
|| fail "produce failed"
|
|
55
|
+
# Group mode on purpose. Assign mode bypasses the coordinator and would pass
|
|
56
|
+
# even when __consumer_offsets cannot be created on a single broker.
|
|
57
|
+
local got
|
|
58
|
+
got=$(docker exec "$container" /opt/kafka/bin/kafka-console-consumer.sh \
|
|
59
|
+
--bootstrap-server broker-1:19092 --topic smoke --from-beginning \
|
|
60
|
+
--max-messages 5 --timeout-ms 60000 2>/dev/null | grep -c '"n"')
|
|
61
|
+
[ "$got" -eq 5 ] || fail "group-mode consume returned $got of 5 messages"
|
|
62
|
+
pass "produced and consumed 5 messages through a consumer group"
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
smoke_flink() {
|
|
66
|
+
retry 40 5 http_ok "http://127.0.0.1:8082/config" || fail "JobManager REST never answered"
|
|
67
|
+
local out
|
|
68
|
+
out=$(docker exec -i flink-jobmanager /opt/flink/bin/sql-client.sh 2>&1 <<'SQL'
|
|
69
|
+
CREATE CATALOG ice WITH ('type'='iceberg','catalog-type'='rest','uri'='http://catalog:8181','warehouse'='s3://warehouse','s3.endpoint'='http://seaweed:8333','s3.path-style-access'='true','s3.access-key-id'='user','s3.secret-access-key'='password');
|
|
70
|
+
SHOW CATALOGS;
|
|
71
|
+
SQL
|
|
72
|
+
)
|
|
73
|
+
grep -q "ice" <<<"$out" || { echo "$out" | tail -20; fail "Iceberg catalog did not register (connector factory or Hadoop classes missing)"; }
|
|
74
|
+
# The image ships --add-opens in config.yaml; mounting a replacement wipes them
|
|
75
|
+
# and Kryo then fails on java.nio buffers at checkpoint time.
|
|
76
|
+
docker exec flink-jobmanager sh -c 'ps ax | grep -q "add-opens=java.base/java.nio"' \
|
|
77
|
+
|| fail "JVM module flags missing from the TaskManager command line"
|
|
78
|
+
pass "Iceberg catalog registered and JVM module flags present"
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
smoke_spark() {
|
|
82
|
+
local out
|
|
83
|
+
out=$(docker exec spark-master /opt/spark/bin/spark-sql -e "
|
|
84
|
+
CREATE NAMESPACE IF NOT EXISTS iceberg.smoke;
|
|
85
|
+
CREATE OR REPLACE TABLE iceberg.smoke.t (id BIGINT) USING iceberg;
|
|
86
|
+
INSERT INTO iceberg.smoke.t VALUES (1),(2),(3);
|
|
87
|
+
SELECT count(*) FROM iceberg.smoke.t;" 2>&1)
|
|
88
|
+
grep -qE "^3$" <<<"$out" || { echo "$out" | tail -20; fail "spark-sql could not round-trip an Iceberg table"; }
|
|
89
|
+
docker exec spark-master sh -c 'ls /tmp/spark-events | head -1' >/dev/null 2>&1 \
|
|
90
|
+
|| fail "no event log written, so the event log directory is not writable"
|
|
91
|
+
pass "spark-sql wrote and read an Iceberg table, event logging works"
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
smoke_ch_lite() {
|
|
95
|
+
retry 30 5 docker exec ch-11 clickhouse-client --password password -q "SELECT 1" \
|
|
96
|
+
|| fail "ch-11 never answered"
|
|
97
|
+
# Databases come from /docker-entrypoint-initdb.d on every fresh server.
|
|
98
|
+
ch ch-11 "SHOW DATABASES" | grep -q feature_store || fail "feature_store database missing"
|
|
99
|
+
ch ch-11 "DROP TABLE IF EXISTS default.smoke" >/dev/null
|
|
100
|
+
ch ch-11 "CREATE TABLE default.smoke (id UInt32) ENGINE=MergeTree ORDER BY id" >/dev/null
|
|
101
|
+
ch ch-11 "INSERT INTO default.smoke SELECT number FROM numbers(10)" >/dev/null
|
|
102
|
+
[ "$(ch ch-11 "SELECT count() FROM default.smoke")" = "10" ] || fail "MergeTree round trip failed"
|
|
103
|
+
pass "databases initialised and MergeTree round trip works"
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
smoke_ch_full() {
|
|
107
|
+
retry 30 5 docker exec ch-21 clickhouse-client --password password -q "SELECT 1" \
|
|
108
|
+
|| fail "ch-21 never answered"
|
|
109
|
+
# Exercises keeper and the dual-stack listeners: replicated DDL fails with
|
|
110
|
+
# "Connection refused" when keeper listens on IPv4 only.
|
|
111
|
+
ch ch-11 "DROP TABLE IF EXISTS default.repl ON CLUSTER '{cluster}' SYNC" >/dev/null 2>&1
|
|
112
|
+
ch ch-11 "CREATE TABLE default.repl ON CLUSTER '{cluster}' (id UInt32) ENGINE=ReplicatedMergeTree ORDER BY id" >/dev/null \
|
|
113
|
+
|| fail "replicated DDL failed, check keeper reachability"
|
|
114
|
+
ch ch-11 "INSERT INTO default.repl SELECT number FROM numbers(50)" >/dev/null
|
|
115
|
+
retry 12 5 bash -c '[ "$(docker exec ch-12 clickhouse-client --password password -q "SELECT count() FROM default.repl" 2>/dev/null)" = "50" ]' \
|
|
116
|
+
|| fail "rows never replicated to the sibling replica"
|
|
117
|
+
[ "$(ch ch-21 "SELECT count() FROM default.repl")" = "0" ] || fail "shard 2 unexpectedly holds shard 1 data"
|
|
118
|
+
pass "replicated table converged on shard 1 and stayed off shard 2"
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
# Catalog registration finishes after the health endpoint starts answering, so
|
|
122
|
+
# poll rather than asserting once. Catalogs load even when their backing service
|
|
123
|
+
# is absent, which is why this can be required in a trino-only profile group.
|
|
124
|
+
catalogs_loaded() {
|
|
125
|
+
local cat
|
|
126
|
+
cat=$(docker exec trino trino --execute "SHOW CATALOGS" 2>/dev/null | tr -d '"')
|
|
127
|
+
for expected in iceberg kafka clickhouse postgres; do
|
|
128
|
+
grep -q "^${expected}$" <<<"$cat" || return 1
|
|
129
|
+
done
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
smoke_trino() {
|
|
133
|
+
retry 40 5 http_ok "http://127.0.0.1:8080/v1/info" || fail "Trino never answered"
|
|
134
|
+
retry 24 5 catalogs_loaded || {
|
|
135
|
+
docker exec trino trino --execute "SHOW CATALOGS" 2>&1 | tail -10
|
|
136
|
+
fail "not every catalog loaded within two minutes"
|
|
137
|
+
}
|
|
138
|
+
docker exec trino trino --execute "SELECT 1" >/dev/null 2>&1 || fail "query execution failed"
|
|
139
|
+
pass "all catalogs loaded and a query ran"
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
smoke_infra() {
|
|
143
|
+
case "$PROFILE" in
|
|
144
|
+
postgres)
|
|
145
|
+
retry 30 5 docker exec postgres pg_isready -U user || fail "postgres never became ready"
|
|
146
|
+
docker exec postgres psql -U user -d odctl -c "SELECT 1" >/dev/null 2>&1 || fail "psql query failed"
|
|
147
|
+
pass "accepting connections and running queries" ;;
|
|
148
|
+
storage)
|
|
149
|
+
retry 30 5 http_reachable "http://127.0.0.1:8333" || fail "S3 API never answered"
|
|
150
|
+
pass "S3 API reachable" ;;
|
|
151
|
+
catalog)
|
|
152
|
+
retry 30 5 http_ok "http://127.0.0.1:8181/v1/config" || fail "REST catalog never answered"
|
|
153
|
+
curl -fsS -X POST -H 'Content-Type: application/json' \
|
|
154
|
+
-d '{"namespace":["smoke"]}' "http://127.0.0.1:8181/v1/namespaces" >/dev/null 2>&1
|
|
155
|
+
curl -fsS "http://127.0.0.1:8181/v1/namespaces" 2>/dev/null | grep -q smoke \
|
|
156
|
+
|| fail "namespace create or list failed"
|
|
157
|
+
pass "namespace created and listed through the REST API" ;;
|
|
158
|
+
valkey)
|
|
159
|
+
retry 30 5 docker exec valkey valkey-cli -u redis://user:password@localhost:6379 ping \
|
|
160
|
+
|| fail "valkey never answered PING"
|
|
161
|
+
pass "responding to PING" ;;
|
|
162
|
+
esac
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
# A profile with no functional assertion yet still has to expose its endpoint.
|
|
166
|
+
smoke_http_only() {
|
|
167
|
+
local url="$1"
|
|
168
|
+
retry 60 5 http_ok "$url" || fail "no HTTP response from $url"
|
|
169
|
+
pass "HTTP endpoint answering at $url"
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
case "$PROFILE" in
|
|
173
|
+
kafka-lite) smoke_kafka kafka ;;
|
|
174
|
+
kafka-full) smoke_kafka kafka-1 ;;
|
|
175
|
+
flink-lite|flink-full) smoke_flink ;;
|
|
176
|
+
spark-lite|spark-full) smoke_spark ;;
|
|
177
|
+
ch-lite) smoke_ch_lite ;;
|
|
178
|
+
ch-full) smoke_ch_full ;;
|
|
179
|
+
trino) smoke_trino ;;
|
|
180
|
+
postgres|storage|catalog|valkey) smoke_infra ;;
|
|
181
|
+
metabase) smoke_http_only "http://127.0.0.1:3000/api/health" ;;
|
|
182
|
+
airflow) smoke_http_only "http://127.0.0.1:8085/api/v2/monitor/health" ;;
|
|
183
|
+
mlflow) smoke_http_only "http://127.0.0.1:5000/health" ;;
|
|
184
|
+
ray-serve) smoke_http_only "http://127.0.0.1:8265" ;;
|
|
185
|
+
lineage) smoke_http_only "http://127.0.0.1:5002/api/v1/namespaces" ;;
|
|
186
|
+
telemetry) smoke_http_only "http://127.0.0.1:19090/-/ready" ;;
|
|
187
|
+
metadata) smoke_http_only "http://127.0.0.1:8585/api/v1/system/version" ;;
|
|
188
|
+
fluss)
|
|
189
|
+
retry 40 5 bash -c 'docker ps --format "{{.Names}}" | grep -q fluss-coordinator' \
|
|
190
|
+
|| fail "coordinator container never appeared"
|
|
191
|
+
pass "coordinator running" ;;
|
|
192
|
+
*)
|
|
193
|
+
echo "ℹ️ $PROFILE: no functional assertion defined, checking containers only"
|
|
194
|
+
[ "$(docker ps -q | wc -l)" -ge 1 ] || fail "no containers running"
|
|
195
|
+
pass "containers running" ;;
|
|
196
|
+
esac
|
|
@@ -3,8 +3,6 @@ name: End-to-End Integration Tests
|
|
|
3
3
|
on:
|
|
4
4
|
workflow_call:
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
6
|
jobs:
|
|
9
7
|
e2e:
|
|
10
8
|
runs-on: ubuntu-latest
|
|
@@ -12,18 +10,30 @@ jobs:
|
|
|
12
10
|
contents: read
|
|
13
11
|
packages: read
|
|
14
12
|
strategy:
|
|
13
|
+
fail-fast: false
|
|
15
14
|
matrix:
|
|
15
|
+
# Profiles are grouped only where they share a large image, so the pull
|
|
16
|
+
# is amortised: the lite and full variants of Kafka, Flink, Spark and
|
|
17
|
+
# ClickHouse. Everything else runs alone, which keeps the failure signal
|
|
18
|
+
# per profile and shortens the critical path.
|
|
16
19
|
profile_group:
|
|
17
|
-
- "metadata"
|
|
18
|
-
- "airflow"
|
|
19
20
|
- "kafka-lite kafka-full"
|
|
20
|
-
- "
|
|
21
|
+
- "flink-lite flink-full"
|
|
21
22
|
- "spark-lite spark-full"
|
|
22
23
|
- "ch-lite ch-full"
|
|
23
|
-
- "
|
|
24
|
-
- "
|
|
25
|
-
- "
|
|
26
|
-
- "
|
|
24
|
+
- "fluss"
|
|
25
|
+
- "trino"
|
|
26
|
+
- "metabase"
|
|
27
|
+
- "mlflow"
|
|
28
|
+
- "ray-serve"
|
|
29
|
+
- "lineage"
|
|
30
|
+
- "telemetry"
|
|
31
|
+
- "metadata"
|
|
32
|
+
- "airflow"
|
|
33
|
+
- "postgres"
|
|
34
|
+
- "storage"
|
|
35
|
+
- "catalog"
|
|
36
|
+
- "valkey"
|
|
27
37
|
steps:
|
|
28
38
|
- name: Checkout repository
|
|
29
39
|
uses: actions/checkout@v4
|
|
@@ -49,31 +59,27 @@ jobs:
|
|
|
49
59
|
|
|
50
60
|
- name: Test Profile Group
|
|
51
61
|
run: |
|
|
52
|
-
|
|
62
|
+
chmod +x .github/scripts/smoke.sh
|
|
53
63
|
for profile in ${{ matrix.profile_group }}; do
|
|
54
64
|
echo "::group::Testing Profile: $profile"
|
|
55
65
|
echo "🚀 Launching $profile..."
|
|
56
|
-
odctl up $profile
|
|
66
|
+
odctl up "$profile"
|
|
57
67
|
|
|
58
|
-
echo "⏳
|
|
59
|
-
|
|
60
|
-
for i in {1..15}; do
|
|
61
|
-
RUNNING=$(docker ps -q | wc -l)
|
|
62
|
-
if [ "$RUNNING" -ge 1 ]; then
|
|
63
|
-
echo "✅ Containers are running!"
|
|
64
|
-
docker ps
|
|
65
|
-
odctl ps --all
|
|
66
|
-
SUCCESS=1
|
|
67
|
-
break
|
|
68
|
-
fi
|
|
69
|
-
echo "Waiting... ($i/15)"
|
|
70
|
-
sleep 2
|
|
71
|
-
done
|
|
68
|
+
echo "⏳ Containers:"
|
|
69
|
+
docker ps
|
|
72
70
|
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
# Liveness alone has never been enough here: every bug fixed in 0.2.x
|
|
72
|
+
# and 0.3.0 left healthy containers behind a broken profile, so the
|
|
73
|
+
# smoke script asserts the profile can actually do its job.
|
|
74
|
+
echo "🔍 Smoke testing $profile..."
|
|
75
|
+
if ! .github/scripts/smoke.sh "$profile"; then
|
|
76
|
+
echo "::error::Smoke test failed for $profile"
|
|
75
77
|
docker ps -a
|
|
76
78
|
odctl ps --all
|
|
79
|
+
for c in $(docker ps -aq); do
|
|
80
|
+
echo "---- logs: $(docker inspect --format '{{.Name}}' "$c") ----"
|
|
81
|
+
docker logs --tail 40 "$c" 2>&1 || true
|
|
82
|
+
done
|
|
77
83
|
exit 1
|
|
78
84
|
fi
|
|
79
85
|
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
name: Main Pipeline
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["main"]
|
|
6
|
+
tags: ["v*"]
|
|
7
|
+
pull_request:
|
|
8
|
+
branches: ["main"]
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
concurrency:
|
|
12
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
13
|
+
cancel-in-progress: true
|
|
14
|
+
|
|
15
|
+
permissions:
|
|
16
|
+
contents: read
|
|
17
|
+
packages: write
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
# Which parts of the tree moved. Used to skip the multi-arch image builds on
|
|
21
|
+
# changes that cannot affect them, which is most changes.
|
|
22
|
+
changes:
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
permissions:
|
|
25
|
+
contents: read
|
|
26
|
+
pull-requests: read
|
|
27
|
+
outputs:
|
|
28
|
+
images: ${{ steps.filter.outputs.images }}
|
|
29
|
+
steps:
|
|
30
|
+
- uses: actions/checkout@v4
|
|
31
|
+
- uses: dorny/paths-filter@v3
|
|
32
|
+
id: filter
|
|
33
|
+
with:
|
|
34
|
+
filters: |
|
|
35
|
+
images:
|
|
36
|
+
- 'src/odctl/resources/deps/**'
|
|
37
|
+
- 'src/odctl/resources/docker/**'
|
|
38
|
+
- '.github/workflows/build-deps.yml'
|
|
39
|
+
- '.github/workflows/build-platform-images.yml'
|
|
40
|
+
|
|
41
|
+
ci:
|
|
42
|
+
uses: ./.github/workflows/ci.yml
|
|
43
|
+
|
|
44
|
+
# Images are pushed only on a tag, so on any other ref this is a
|
|
45
|
+
# build-and-discard. Run it when the build inputs changed, so a broken
|
|
46
|
+
# Dockerfile surfaces before release instead of at tag time, and skip it
|
|
47
|
+
# otherwise. That removes two multi-arch builds from the typical change.
|
|
48
|
+
build-platform:
|
|
49
|
+
needs: [ci, changes]
|
|
50
|
+
if: >-
|
|
51
|
+
startsWith(github.ref, 'refs/tags/v') ||
|
|
52
|
+
github.event_name == 'workflow_dispatch' ||
|
|
53
|
+
needs.changes.outputs.images == 'true'
|
|
54
|
+
uses: ./.github/workflows/build-platform-images.yml
|
|
55
|
+
|
|
56
|
+
build-deps:
|
|
57
|
+
needs: [ci, changes]
|
|
58
|
+
if: >-
|
|
59
|
+
startsWith(github.ref, 'refs/tags/v') ||
|
|
60
|
+
github.event_name == 'workflow_dispatch' ||
|
|
61
|
+
needs.changes.outputs.images == 'true'
|
|
62
|
+
uses: ./.github/workflows/build-deps.yml
|
|
63
|
+
|
|
64
|
+
# Tags and manual runs only: the e2e job pulls images at the CLI's own version,
|
|
65
|
+
# and those exist on ghcr only once a tag has published them.
|
|
66
|
+
e2e:
|
|
67
|
+
needs: [build-platform, build-deps]
|
|
68
|
+
if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch'
|
|
69
|
+
uses: ./.github/workflows/e2e-test.yml
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
# Publishing stays tied to publishing a release, so the release notes remain the
|
|
4
|
+
# trigger. What is new is the gate below: a release event fires independently of
|
|
5
|
+
# CI, which is how v0.3.0 reached PyPI while the pipeline's e2e job was failing.
|
|
6
|
+
# This job now waits for the tag's Main Pipeline run and refuses to publish
|
|
7
|
+
# unless it succeeded.
|
|
8
|
+
on:
|
|
9
|
+
release:
|
|
10
|
+
types: [published]
|
|
11
|
+
workflow_dispatch:
|
|
12
|
+
inputs:
|
|
13
|
+
tag:
|
|
14
|
+
description: "Tag to publish, for example v0.3.1"
|
|
15
|
+
required: true
|
|
16
|
+
skip_pipeline_check:
|
|
17
|
+
description: "Publish even if the tag's pipeline is not green"
|
|
18
|
+
type: boolean
|
|
19
|
+
default: false
|
|
20
|
+
|
|
21
|
+
concurrency:
|
|
22
|
+
group: ${{ github.workflow }}-${{ github.event.release.tag_name || inputs.tag }}
|
|
23
|
+
cancel-in-progress: false
|
|
24
|
+
|
|
25
|
+
jobs:
|
|
26
|
+
pypi-publish:
|
|
27
|
+
name: Build and publish Python package
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
# These permissions are strictly required for PyPI OIDC Trusted Publishing;
|
|
30
|
+
# actions:read is what lets the gate query the pipeline's conclusion.
|
|
31
|
+
permissions:
|
|
32
|
+
contents: read
|
|
33
|
+
id-token: write
|
|
34
|
+
actions: read
|
|
35
|
+
|
|
36
|
+
steps:
|
|
37
|
+
- name: Checkout source code
|
|
38
|
+
uses: actions/checkout@v7
|
|
39
|
+
with:
|
|
40
|
+
ref: ${{ github.event.release.tag_name || inputs.tag }}
|
|
41
|
+
|
|
42
|
+
- name: Require a green Main Pipeline for this tag
|
|
43
|
+
if: ${{ !inputs.skip_pipeline_check }}
|
|
44
|
+
env:
|
|
45
|
+
GH_TOKEN: ${{ github.token }}
|
|
46
|
+
TAG: ${{ github.event.release.tag_name || inputs.tag }}
|
|
47
|
+
run: |
|
|
48
|
+
set -euo pipefail
|
|
49
|
+
# Wait rather than fail outright: a release is usually published moments
|
|
50
|
+
# after the tag is pushed, so the pipeline is still running e2e.
|
|
51
|
+
deadline=$(( SECONDS + 45 * 60 ))
|
|
52
|
+
while :; do
|
|
53
|
+
run=$(gh api "repos/${GITHUB_REPOSITORY}/actions/runs?event=push&per_page=100" \
|
|
54
|
+
--jq "[.workflow_runs[]
|
|
55
|
+
| select(.name == \"Main Pipeline\" and .head_branch == \"${TAG}\")]
|
|
56
|
+
| sort_by(.created_at) | last // empty")
|
|
57
|
+
if [ -z "$run" ]; then
|
|
58
|
+
echo "::error::No Main Pipeline run found for tag ${TAG}. Push the tag first."
|
|
59
|
+
exit 1
|
|
60
|
+
fi
|
|
61
|
+
status=$(jq -r '.status' <<<"$run")
|
|
62
|
+
concl=$(jq -r '.conclusion' <<<"$run")
|
|
63
|
+
url=$(jq -r '.html_url' <<<"$run")
|
|
64
|
+
if [ "$status" = "completed" ]; then
|
|
65
|
+
if [ "$concl" = "success" ]; then
|
|
66
|
+
echo "✅ Main Pipeline for ${TAG} succeeded: $url"
|
|
67
|
+
exit 0
|
|
68
|
+
fi
|
|
69
|
+
echo "::error::Main Pipeline for ${TAG} concluded '${concl}', refusing to publish: $url"
|
|
70
|
+
exit 1
|
|
71
|
+
fi
|
|
72
|
+
if [ "$SECONDS" -ge "$deadline" ]; then
|
|
73
|
+
echo "::error::Main Pipeline for ${TAG} still '${status}' after 45 minutes: $url"
|
|
74
|
+
exit 1
|
|
75
|
+
fi
|
|
76
|
+
echo "⏳ Main Pipeline for ${TAG} is ${status}; waiting: $url"
|
|
77
|
+
sleep 30
|
|
78
|
+
done
|
|
79
|
+
|
|
80
|
+
- name: Install uv
|
|
81
|
+
uses: astral-sh/setup-uv@v7
|
|
82
|
+
|
|
83
|
+
- name: Set up Python
|
|
84
|
+
run: uv python install 3.10
|
|
85
|
+
|
|
86
|
+
- name: Build Wheel and sdist
|
|
87
|
+
run: uv build
|
|
88
|
+
|
|
89
|
+
- name: Publish package distributions to PyPI
|
|
90
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -175,8 +175,12 @@ def stop_stack(
|
|
|
175
175
|
"""
|
|
176
176
|
path = get_compose_path(compose_filename)
|
|
177
177
|
stack_client = _create_client(compose_files=[str(path)], profiles=profiles)
|
|
178
|
-
# volumes=True will remove named volumes defined in the compose file
|
|
179
|
-
|
|
178
|
+
# volumes=True will remove named volumes defined in the compose file.
|
|
179
|
+
# remove_orphans catches containers whose service no longer exists in the
|
|
180
|
+
# compose file, which teardown would otherwise leave running forever: an
|
|
181
|
+
# upgrade that drops or renames a service (ch-init-lite in 0.2.0, the
|
|
182
|
+
# flink1-* services in 0.3.0) leaves the old container holding its name.
|
|
183
|
+
stack_client.compose.down(volumes=remove_volumes, remove_orphans=True)
|
|
180
184
|
|
|
181
185
|
|
|
182
186
|
def get_managed_containers(execution_plan: Dict[str, List[str]]) -> List[Any]:
|
|
@@ -53,7 +53,7 @@ services:
|
|
|
53
53
|
# Object Storage (SeaweedFS - Single Node)
|
|
54
54
|
# ==========================================
|
|
55
55
|
seaweed:
|
|
56
|
-
image: chrislusf/seaweedfs:4.22
|
|
56
|
+
image: &seaweed-image chrislusf/seaweedfs:4.22
|
|
57
57
|
container_name: seaweed
|
|
58
58
|
ports:
|
|
59
59
|
- "8333:8333" # S3 API
|
|
@@ -78,7 +78,7 @@ services:
|
|
|
78
78
|
retries: 10
|
|
79
79
|
|
|
80
80
|
seaweed-init:
|
|
81
|
-
image:
|
|
81
|
+
image: *seaweed-image
|
|
82
82
|
container_name: seaweed-init
|
|
83
83
|
depends_on:
|
|
84
84
|
seaweed:
|
|
@@ -19,7 +19,7 @@ services:
|
|
|
19
19
|
# [LITE] Single Broker Cluster (Container: kafka)
|
|
20
20
|
# ==========================================
|
|
21
21
|
kafka:
|
|
22
|
-
image: apache/kafka:4.3.0
|
|
22
|
+
image: &kafka-image apache/kafka:4.3.0
|
|
23
23
|
container_name: kafka
|
|
24
24
|
ports:
|
|
25
25
|
- "9092:9092"
|
|
@@ -54,7 +54,7 @@ services:
|
|
|
54
54
|
# [FULL] 3-Node Broker Cluster
|
|
55
55
|
# ==========================================
|
|
56
56
|
kafka-1:
|
|
57
|
-
image:
|
|
57
|
+
image: *kafka-image
|
|
58
58
|
container_name: kafka-1
|
|
59
59
|
ports:
|
|
60
60
|
- "9092:9092"
|
|
@@ -74,7 +74,7 @@ services:
|
|
|
74
74
|
mem_limit: 1500m
|
|
75
75
|
|
|
76
76
|
kafka-2:
|
|
77
|
-
image:
|
|
77
|
+
image: *kafka-image
|
|
78
78
|
container_name: kafka-2
|
|
79
79
|
ports:
|
|
80
80
|
- "9093:9093"
|
|
@@ -94,7 +94,7 @@ services:
|
|
|
94
94
|
mem_limit: 1500m
|
|
95
95
|
|
|
96
96
|
kafka-3:
|
|
97
|
-
image:
|
|
97
|
+
image: *kafka-image
|
|
98
98
|
container_name: kafka-3
|
|
99
99
|
ports:
|
|
100
100
|
- "9094:9094"
|
|
@@ -117,7 +117,7 @@ services:
|
|
|
117
117
|
# Open Source Kafka Connect
|
|
118
118
|
# ==========================================
|
|
119
119
|
connect:
|
|
120
|
-
image:
|
|
120
|
+
image: *kafka-image
|
|
121
121
|
container_name: connect
|
|
122
122
|
restart: unless-stopped
|
|
123
123
|
ports:
|
|
@@ -33,7 +33,7 @@ services:
|
|
|
33
33
|
# [PROFILE: metadata] OpenMetadata DB Migration
|
|
34
34
|
# ==========================================
|
|
35
35
|
openmetadata-migrate:
|
|
36
|
-
image: docker.getcollate.io/openmetadata/server:1.12.6
|
|
36
|
+
image: &om-server-image docker.getcollate.io/openmetadata/server:1.12.6
|
|
37
37
|
container_name: openmetadata-migrate
|
|
38
38
|
command: "./bootstrap/openmetadata-ops.sh migrate"
|
|
39
39
|
profiles:
|
|
@@ -63,7 +63,7 @@ services:
|
|
|
63
63
|
# [PROFILE: metadata] OpenMetadata Server
|
|
64
64
|
# ==========================================
|
|
65
65
|
openmetadata-server:
|
|
66
|
-
image:
|
|
66
|
+
image: *om-server-image
|
|
67
67
|
container_name: openmetadata-server
|
|
68
68
|
restart: always
|
|
69
69
|
profiles:
|
|
@@ -7,7 +7,7 @@ services:
|
|
|
7
7
|
# [PROFILE: fluss] Streaming Storage Engine
|
|
8
8
|
# ==========================================
|
|
9
9
|
fluss-coordinator:
|
|
10
|
-
image: apache/fluss:0.8.0-incubating
|
|
10
|
+
image: &fluss-image apache/fluss:0.8.0-incubating
|
|
11
11
|
container_name: fluss-coordinator
|
|
12
12
|
command: coordinatorServer
|
|
13
13
|
profiles: ["fluss"]
|
|
@@ -28,7 +28,7 @@ services:
|
|
|
28
28
|
remote.data.dir: /tmp/fluss/remote-data
|
|
29
29
|
|
|
30
30
|
fluss-tablet-1:
|
|
31
|
-
image:
|
|
31
|
+
image: *fluss-image
|
|
32
32
|
container_name: fluss-tablet-1
|
|
33
33
|
command: tabletServer
|
|
34
34
|
profiles: ["fluss"]
|
|
@@ -15,10 +15,13 @@ RUN --mount=type=cache,target=/root/.gradle \
|
|
|
15
15
|
# ==========================================
|
|
16
16
|
FROM gradle:8.6-jdk17 AS gradle-resolver
|
|
17
17
|
WORKDIR /app
|
|
18
|
-
COPY build.gradle .
|
|
19
|
-
# Let Gradle do the heavy transitive lifting for both Connect and Flink
|
|
18
|
+
COPY build.gradle versions.env ./
|
|
19
|
+
# Let Gradle do the heavy transitive lifting for both Connect and Flink, using the
|
|
20
|
+
# same pins the fetch scripts read so the two cannot drift. See issue #5.
|
|
20
21
|
RUN --mount=type=cache,target=/home/gradle/.gradle \
|
|
21
|
-
|
|
22
|
+
. ./versions.env \
|
|
23
|
+
&& gradle copyAllDependencies --no-daemon \
|
|
24
|
+
-PflinkMinor="${FLINK_MINOR}" -PhadoopVersion="${HADOOP_V}"
|
|
22
25
|
|
|
23
26
|
# ==========================================
|
|
24
27
|
# Stage 3: Direct Downloader (Uber JARs & ZIPs)
|
|
@@ -29,6 +32,7 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
|
|
29
32
|
apt-get update && apt-get install -y curl tar unzip && rm -rf /var/lib/apt/lists/*
|
|
30
33
|
WORKDIR /downloads
|
|
31
34
|
# Copy the standalone fetch scripts
|
|
35
|
+
COPY versions.env .
|
|
32
36
|
COPY fetch_standalone_plugins.sh .
|
|
33
37
|
COPY fetch_flink_dependencies.sh .
|
|
34
38
|
COPY fetch_spark_dependencies.sh .
|
|
@@ -11,8 +11,10 @@ repositories {
|
|
|
11
11
|
|
|
12
12
|
ext {
|
|
13
13
|
confluentVersion = '8.2.0'
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
// Supplied by the Dockerfile from versions.env. The '+' resolves the latest
|
|
15
|
+
// patch of the pinned minor, which is what the fetch scripts do too.
|
|
16
|
+
flinkVersion = "${project.findProperty('flinkMinor') ?: '2.1'}.+"
|
|
17
|
+
hadoopVersion = project.findProperty('hadoopVersion') ?: '3.3.6'
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
configurations {
|
|
@@ -9,21 +9,20 @@ fetch_artifact() {
|
|
|
9
9
|
if [ "$DRY_RUN" -eq 1 ]; then echo " 🔍 Checking: $2"; curl -sL -I -f "$2" > /dev/null || exit 1;
|
|
10
10
|
else echo " ⬇️ Downloading: $2"; curl -sL -f -o "$1" "$2"; fi
|
|
11
11
|
}
|
|
12
|
+
# Versions come from versions.env so a bump is one edit rather than four kept
|
|
13
|
+
# in step. See issue #5.
|
|
14
|
+
# shellcheck source=versions.env
|
|
15
|
+
. "$(dirname "$0")/versions.env"
|
|
16
|
+
|
|
12
17
|
get_maven_version() {
|
|
13
18
|
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
19
|
}
|
|
15
20
|
|
|
16
21
|
echo "▶️ Resolving Flink Versions..."
|
|
17
22
|
|
|
18
|
-
# 🔒 PINNED: Flink 2.1, not the latest 2.x. Iceberg publishes
|
|
19
|
-
# iceberg-flink-runtime for 2.0 and 2.1 only, and Fluss has no 2.0 build, so 2.1
|
|
20
|
-
# is the one minor where every connector this stack needs exists. See issue #9.
|
|
21
|
-
FLINK_MINOR="2.1"
|
|
22
23
|
FLINK_V=$(get_maven_version "org/apache/flink/flink-core" "${FLINK_MINOR}\.[0-9]+")
|
|
23
24
|
if [ -z "$FLINK_V" ]; then echo "❌ Error: Could not resolve Flink ${FLINK_MINOR} patch version!"; exit 1; fi
|
|
24
25
|
|
|
25
|
-
# 🔒 HARDCODED: Lock Iceberg to 1.11.0 to match Spark and PyIceberg sidecar
|
|
26
|
-
ICEBERG_V="1.11.0"
|
|
27
26
|
|
|
28
27
|
echo "▶️ Fetching Flink Dependencies for ${FLINK_MINOR} (Iceberg ${ICEBERG_V})..."
|
|
29
28
|
make_dir "flink/2.x"
|
|
@@ -9,6 +9,11 @@ fetch_artifact() {
|
|
|
9
9
|
if [ "$DRY_RUN" -eq 1 ]; then echo " 🔍 Checking: $2"; curl -sL -I -f "$2" > /dev/null || exit 1;
|
|
10
10
|
else echo " ⬇️ Downloading: $2"; curl -sL -f -o "$1" "$2"; fi
|
|
11
11
|
}
|
|
12
|
+
# Versions come from versions.env so a bump is one edit rather than several
|
|
13
|
+
# kept in step. See issue #5.
|
|
14
|
+
# shellcheck source=versions.env
|
|
15
|
+
. "$(dirname "$0")/versions.env"
|
|
16
|
+
|
|
12
17
|
get_maven_version() {
|
|
13
18
|
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
19
|
}
|
|
@@ -16,9 +21,8 @@ get_maven_version() {
|
|
|
16
21
|
echo "▶️ Resolving Spark Versions..."
|
|
17
22
|
SCALA_V="2.13"
|
|
18
23
|
|
|
19
|
-
# 🔒 HARDCODED: Lock to Spark 4.1.x
|
|
24
|
+
# 🔒 HARDCODED: Lock to Spark 4.1.x; Iceberg comes from versions.env
|
|
20
25
|
SPARK_COMPAT_MINOR="4.1"
|
|
21
|
-
ICEBERG_V="1.11.0"
|
|
22
26
|
|
|
23
27
|
# Keep OpenLineage dynamic as it relies on frequent patch updates
|
|
24
28
|
OL_SPARK_V=$(get_maven_version "io/openlineage/openlineage-spark_${SCALA_V}" "[0-9]+\.[0-9]+\.[0-9]+")
|
|
@@ -9,14 +9,18 @@ fetch_artifact() {
|
|
|
9
9
|
if [ "$DRY_RUN" -eq 1 ]; then echo " 🔍 Checking: $2"; curl -sL -I -f "$2" > /dev/null || exit 1;
|
|
10
10
|
else echo " ⬇️ Downloading: $2"; curl -sL -f -o "$1" "$2"; fi
|
|
11
11
|
}
|
|
12
|
+
# Versions come from versions.env so a bump is one edit rather than several
|
|
13
|
+
# kept in step. See issue #5.
|
|
14
|
+
# shellcheck source=versions.env
|
|
15
|
+
. "$(dirname "$0")/versions.env"
|
|
16
|
+
|
|
12
17
|
get_maven_version() {
|
|
13
18
|
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
19
|
}
|
|
15
20
|
|
|
16
21
|
echo "▶️ Resolving Shared & Connector Versions..."
|
|
17
22
|
|
|
18
|
-
# 🔒 HARDCODED: Lock core dependencies
|
|
19
|
-
ICEBERG_V="1.11.0"
|
|
23
|
+
# 🔒 HARDCODED: Lock core dependencies; Iceberg comes from versions.env
|
|
20
24
|
DEB_V="3.5.1.Final"
|
|
21
25
|
POSTGRES_V="42.7.3" # Latest stable JDBC 42.x series
|
|
22
26
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Single source of truth for the versions the deps image builds against.
|
|
2
|
+
#
|
|
3
|
+
# Sourced by every fetch script and passed into the Gradle stage as build args,
|
|
4
|
+
# so a bump is one edit here rather than four in step. See issue #5.
|
|
5
|
+
#
|
|
6
|
+
# The one value that lives outside this file is the Flink runtime image tag in
|
|
7
|
+
# compose-flink.yml, which Docker Compose resolves before any of this runs. Keep
|
|
8
|
+
# its minor equal to FLINK_MINOR below.
|
|
9
|
+
|
|
10
|
+
# Flink minor series. Pinned to 2.1 because Iceberg publishes
|
|
11
|
+
# iceberg-flink-runtime for 2.0 and 2.1 only, and fluss-flink has no 2.0 build.
|
|
12
|
+
FLINK_MINOR=2.1
|
|
13
|
+
|
|
14
|
+
# Iceberg client libraries, shared by the Spark, Flink, and standalone fetches so
|
|
15
|
+
# every engine and the PyIceberg sidecar agree.
|
|
16
|
+
ICEBERG_V=1.11.0
|
|
17
|
+
|
|
18
|
+
# Hadoop client jars, used by the Gradle stage for the Flink classpath.
|
|
19
|
+
HADOOP_V=3.3.6
|
|
@@ -235,7 +235,7 @@ stacks:
|
|
|
235
235
|
usage:
|
|
236
236
|
mlflow: |
|
|
237
237
|
• MLflow Tracking UI & Artifact Registry Server:
|
|
238
|
-
* External (Localhost): http://localhost:
|
|
238
|
+
* External (Localhost): http://localhost:5000
|
|
239
239
|
* Internal (Docker): http://mlflow:5000
|
|
240
240
|
ray-serve: |
|
|
241
241
|
• Ray Serve Inference API Proxy:
|
|
@@ -65,7 +65,9 @@ def test_docker_actions(monkeypatch):
|
|
|
65
65
|
mock_client.compose.up.assert_called_with(detach=False)
|
|
66
66
|
|
|
67
67
|
docker.stop_stack("dummy.yml", ["prof1"], remove_volumes=True)
|
|
68
|
-
|
|
68
|
+
# remove_orphans is what clears containers whose service was dropped
|
|
69
|
+
# from the compose file.
|
|
70
|
+
mock_client.compose.down.assert_called_with(volumes=True, remove_orphans=True)
|
|
69
71
|
|
|
70
72
|
|
|
71
73
|
def test_restart_managed_containers(monkeypatch):
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
name: Main Pipeline
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: ["main"]
|
|
6
|
-
tags: ["v*"]
|
|
7
|
-
pull_request:
|
|
8
|
-
branches: ["main"]
|
|
9
|
-
workflow_dispatch:
|
|
10
|
-
|
|
11
|
-
concurrency:
|
|
12
|
-
group: ${{ github.workflow }}-${{ github.ref }}
|
|
13
|
-
cancel-in-progress: true
|
|
14
|
-
|
|
15
|
-
permissions:
|
|
16
|
-
contents: read
|
|
17
|
-
packages: write
|
|
18
|
-
|
|
19
|
-
jobs:
|
|
20
|
-
ci:
|
|
21
|
-
uses: ./.github/workflows/ci.yml
|
|
22
|
-
|
|
23
|
-
build-platform:
|
|
24
|
-
needs: ci
|
|
25
|
-
uses: ./.github/workflows/build-platform-images.yml
|
|
26
|
-
|
|
27
|
-
build-deps:
|
|
28
|
-
needs: ci
|
|
29
|
-
uses: ./.github/workflows/build-deps.yml
|
|
30
|
-
|
|
31
|
-
e2e:
|
|
32
|
-
needs: [build-platform, build-deps]
|
|
33
|
-
if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch'
|
|
34
|
-
uses: ./.github/workflows/e2e-test.yml
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
name: Publish to PyPI
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
release:
|
|
5
|
-
types: [published] # ONLY triggers when a drafted release is published
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
concurrency:
|
|
9
|
-
group: ${{ github.workflow }}-${{ github.ref }}
|
|
10
|
-
cancel-in-progress: true
|
|
11
|
-
jobs:
|
|
12
|
-
pypi-publish:
|
|
13
|
-
name: Build and publish Python package
|
|
14
|
-
runs-on: ubuntu-latest
|
|
15
|
-
# These permissions are strictly required for PyPI OIDC Trusted Publishing
|
|
16
|
-
permissions:
|
|
17
|
-
contents: read
|
|
18
|
-
id-token: write
|
|
19
|
-
|
|
20
|
-
steps:
|
|
21
|
-
- name: Checkout source code
|
|
22
|
-
uses: actions/checkout@v7
|
|
23
|
-
|
|
24
|
-
- name: Install uv
|
|
25
|
-
uses: astral-sh/setup-uv@v7
|
|
26
|
-
|
|
27
|
-
- name: Set up Python
|
|
28
|
-
run: uv python install 3.10
|
|
29
|
-
|
|
30
|
-
- name: Build Wheel and sdist
|
|
31
|
-
run: uv build
|
|
32
|
-
|
|
33
|
-
- name: Publish package distributions to PyPI
|
|
34
|
-
uses: pypa/gh-action-pypi-publish@release/v1
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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
|