@melaya/runner 1.1.82 → 1.1.83

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.
@@ -1,217 +1,217 @@
1
- #!/usr/bin/env python3
2
- """
3
- localDbProbe.py - one-shot, read-only database reachability probe run BY THE
4
- RUNNER on behalf of the Melaya server.
5
-
6
- Why it exists: some databases sit behind an IP allow-list or inside a VPC that
7
- the Melaya cloud can never reach, but the user's own runner (on their LAN /
8
- bastion) can. The server hands us the resolved credentials over the already-
9
- authenticated Socket.IO channel; we open a single real connection from THIS
10
- machine's network vantage point, run a trivial read-only probe, and print the
11
- outcome. We never persist the credentials and never write to the database.
12
-
13
- Contract:
14
- stdin : one JSON object {"service": "...", "creds": { ... }}
15
- stdout : one JSON object {"ok": bool, "message"|"error": "..."}
16
- Exit code is always 0 (the result is the JSON on stdout); any crash is caught
17
- and reported as {"ok": false, "error": ...}.
18
- """
19
- import sys
20
- import json
21
- import base64
22
- import hashlib
23
- import urllib.parse
24
-
25
-
26
- def _out(ok, text):
27
- key = "message" if ok else "error"
28
- sys.stdout.write(json.dumps({"ok": bool(ok), key: str(text)[:300]}))
29
- sys.stdout.flush()
30
- sys.exit(0)
31
-
32
-
33
- def _pick(creds, *keys):
34
- for k in keys:
35
- v = (creds.get(k) or "").strip()
36
- if v:
37
- return v
38
- return ""
39
-
40
-
41
- _PLACEHOLDER_HOSTS = {"host", "hostname", "your-host", "your_host", "<host>", "myhost", "dbhost", "example.com"}
42
-
43
-
44
- def _friendly_conn_error(e, host, port, engine):
45
- """Turn a raw driver/socket exception into an actionable message. The most
46
- common failure is a copy-pasted EXAMPLE DSN (host == 'host') or a Docker/VPC
47
- hostname that doesn't resolve from the runner's machine."""
48
- import socket
49
- msg = str(e) or e.__class__.__name__
50
- low = msg.lower()
51
- host = host or ""
52
- is_dns = isinstance(e, socket.gaierror) or "nodename nor servname" in low \
53
- or "name or service not known" in low or "getaddrinfo" in low \
54
- or "name does not resolve" in low or "no address associated" in low
55
- if is_dns:
56
- if host.lower() in _PLACEHOLDER_HOSTS:
57
- return (f"the host is literally \"{host}\" - that's the EXAMPLE DSN, not your database. "
58
- f"Paste your real connection string with the actual host, e.g. an AWS RDS / Cloud SQL "
59
- f"endpoint: {engine.lower()}://user:pass@your-db.abc123.us-east-1.rds.amazonaws.com:{port}/db.")
60
- if host and "." not in host and host not in ("localhost",):
61
- return (f"can't resolve host \"{host}\" from the runner's machine. A bare name like this is "
62
- f"usually a Docker/compose service or a VPC-internal alias that only resolves elsewhere - "
63
- f"use the host that's reachable from this machine (e.g. your RDS/Cloud SQL endpoint, or a VPN/bastion host).")
64
- return (f"can't resolve host \"{host}\" from the runner's machine - check the host in your DSN, and that "
65
- f"this machine can reach it (DNS + network route; for a private RDS/VPC DB the runner must sit in the VPC or on an allow-listed IP).")
66
- if isinstance(e, ConnectionRefusedError) or "refused" in low:
67
- return f"host \"{host}\" reachable but port {port} refused the connection - is {engine} listening there / bound to that interface?"
68
- if isinstance(e, (TimeoutError,)) or "timed out" in low or "timeout" in low:
69
- return f"timed out connecting to {host}:{port} - firewall, IP allow-list, or wrong host/port?"
70
- if "password authentication failed" in low or "authentication failed" in low or "access denied" in low:
71
- return "authentication failed - check the username/password in the DSN."
72
- if 'database "' in low and "does not exist" in low or "unknown database" in low:
73
- return f"connected, but the database name in the DSN doesn't exist: {msg}"
74
- return msg
75
-
76
-
77
- # ── PostgreSQL (asyncpg) ─────────────────────────────────────────────────────
78
- def probe_postgres(creds):
79
- dsn = _pick(creds, "dsn", "POSTGRES_DSN")
80
- if not dsn:
81
- _out(False, "Connection DSN required")
82
- try:
83
- import asyncio
84
- import asyncpg # type: ignore
85
- except ImportError:
86
- _out(False, "asyncpg not installed on this runner (pip install asyncpg)")
87
- p = urllib.parse.urlparse(dsn)
88
- host, port = (p.hostname or ""), (p.port or 5432)
89
- if not host:
90
- _out(False, "PostgreSQL connection failed: no host in the DSN - expected postgresql://user:pass@HOST:5432/db")
91
-
92
- async def _run():
93
- conn = await asyncpg.connect(dsn, timeout=10)
94
- try:
95
- await conn.fetchval("SELECT 1")
96
- ver = await conn.fetchval("SELECT version()")
97
- finally:
98
- await conn.close()
99
- return ver or ""
100
-
101
- try:
102
- ver = asyncio.run(asyncio.wait_for(_run(), timeout=15))
103
- short = str(ver).split(" on ")[0] if ver else ""
104
- _out(True, f"Connected to PostgreSQL{(' - ' + short) if short else ''}")
105
- except Exception as e: # noqa: BLE001
106
- _out(False, f"PostgreSQL connection failed: {_friendly_conn_error(e, host, port, 'PostgreSQL')}")
107
-
108
-
109
- # ── MySQL / MariaDB (PyMySQL - pure-python, sync) ────────────────────────────
110
- def probe_mysql(creds):
111
- dsn = _pick(creds, "dsn", "MYSQL_DSN")
112
- if not dsn:
113
- _out(False, "Connection DSN required")
114
- try:
115
- import pymysql # type: ignore
116
- except ImportError:
117
- _out(False, "PyMySQL not installed on this runner (pip install PyMySQL)")
118
- p = urllib.parse.urlparse(dsn)
119
- host, port = (p.hostname or ""), (p.port or 3306)
120
- if not host:
121
- _out(False, "MySQL connection failed: no host in the DSN - expected mysql://user:pass@HOST:3306/db")
122
- try:
123
- conn = pymysql.connect(
124
- host=host,
125
- port=port,
126
- user=urllib.parse.unquote(p.username or ""),
127
- password=urllib.parse.unquote(p.password or ""),
128
- db=(p.path or "").lstrip("/") or None,
129
- connect_timeout=10,
130
- read_timeout=10,
131
- )
132
- try:
133
- with conn.cursor() as cur:
134
- cur.execute("SELECT VERSION()")
135
- row = cur.fetchone()
136
- ver = (row[0] if row else "") or ""
137
- finally:
138
- conn.close()
139
- _out(True, f"Connected to MySQL{(' - ' + str(ver)) if ver else ''}")
140
- except Exception as e: # noqa: BLE001
141
- _out(False, f"MySQL connection failed: {_friendly_conn_error(e, host, port, 'MySQL')}")
142
-
143
-
144
- # ── Snowflake (key-pair JWT, mirrors the server handler) ─────────────────────
145
- def probe_snowflake(creds):
146
- account = _pick(creds, "account", "SNOWFLAKE_ACCOUNT")
147
- user = _pick(creds, "user", "SNOWFLAKE_USER")
148
- pk = _pick(creds, "private_key", "SNOWFLAKE_PRIVATE_KEY")
149
- if not account or not user or not pk:
150
- _out(False, "Account + Username + Private Key required")
151
- try:
152
- from cryptography.hazmat.primitives import serialization # type: ignore
153
- import requests # type: ignore
154
- except ImportError:
155
- _out(False, "cryptography/requests not installed on this runner")
156
-
157
- def b64url(b):
158
- if isinstance(b, str):
159
- b = b.encode()
160
- return base64.urlsafe_b64encode(b).rstrip(b"=").decode()
161
-
162
- try:
163
- import time
164
- key = serialization.load_pem_private_key(pk.encode(), password=None)
165
- der_spki = key.public_key().public_bytes(
166
- serialization.Encoding.DER,
167
- serialization.PublicFormat.SubjectPublicKeyInfo,
168
- )
169
- fp = "SHA256:" + base64.b64encode(hashlib.sha256(der_spki).digest()).decode()
170
- acct_upper = account.upper()
171
- if "." in acct_upper:
172
- acct_upper = acct_upper.split(".")[0]
173
- qual = f"{acct_upper}.{user.upper()}"
174
- now = int(time.time())
175
- header = b64url(json.dumps({"alg": "RS256", "typ": "JWT"}))
176
- payload = b64url(json.dumps(
177
- {"iss": f"{qual}.{fp}", "sub": qual, "iat": now, "exp": now + 3540}))
178
- signing_input = f"{header}.{payload}".encode()
179
- from cryptography.hazmat.primitives import hashes # type: ignore
180
- from cryptography.hazmat.primitives.asymmetric import padding # type: ignore
181
- sig = key.sign(signing_input, padding.PKCS1v15(), hashes.SHA256())
182
- jwt = f"{header}.{payload}.{b64url(sig)}"
183
- host = f"https://{account.replace('_', '-').lower()}.snowflakecomputing.com"
184
- r = requests.post(
185
- f"{host}/api/v2/statements",
186
- headers={
187
- "Authorization": f"Bearer {jwt}",
188
- "X-Snowflake-Authorization-Token-Type": "KEYPAIR_JWT",
189
- "Content-Type": "application/json",
190
- "Accept": "application/json",
191
- },
192
- json={"statement": "SELECT CURRENT_VERSION()", "timeout": 20},
193
- timeout=15,
194
- )
195
- if r.ok:
196
- v = ""
197
- try:
198
- v = (r.json().get("data") or [[None]])[0][0] or ""
199
- except Exception: # noqa: BLE001
200
- pass
201
- _out(True, f"Connected to Snowflake{(' (v' + str(v) + ')') if v else ''}")
202
- _out(False, f"Snowflake auth/query failed: HTTP {r.status_code} {r.text[:160]}")
203
- except Exception as e: # noqa: BLE001
204
- _out(False, f"Snowflake connection failed: {e}")
205
-
206
-
207
- # ── Databricks (PAT or OAuth M2M, mirrors the server handler) ────────────────
1
+ #!/usr/bin/env python3
2
+ """
3
+ localDbProbe.py - one-shot, read-only database reachability probe run BY THE
4
+ RUNNER on behalf of the Melaya server.
5
+
6
+ Why it exists: some databases sit behind an IP allow-list or inside a VPC that
7
+ the Melaya cloud can never reach, but the user's own runner (on their LAN /
8
+ bastion) can. The server hands us the resolved credentials over the already-
9
+ authenticated Socket.IO channel; we open a single real connection from THIS
10
+ machine's network vantage point, run a trivial read-only probe, and print the
11
+ outcome. We never persist the credentials and never write to the database.
12
+
13
+ Contract:
14
+ stdin : one JSON object {"service": "...", "creds": { ... }}
15
+ stdout : one JSON object {"ok": bool, "message"|"error": "..."}
16
+ Exit code is always 0 (the result is the JSON on stdout); any crash is caught
17
+ and reported as {"ok": false, "error": ...}.
18
+ """
19
+ import sys
20
+ import json
21
+ import base64
22
+ import hashlib
23
+ import urllib.parse
24
+
25
+
26
+ def _out(ok, text):
27
+ key = "message" if ok else "error"
28
+ sys.stdout.write(json.dumps({"ok": bool(ok), key: str(text)[:300]}))
29
+ sys.stdout.flush()
30
+ sys.exit(0)
31
+
32
+
33
+ def _pick(creds, *keys):
34
+ for k in keys:
35
+ v = (creds.get(k) or "").strip()
36
+ if v:
37
+ return v
38
+ return ""
39
+
40
+
41
+ _PLACEHOLDER_HOSTS = {"host", "hostname", "your-host", "your_host", "<host>", "myhost", "dbhost", "example.com"}
42
+
43
+
44
+ def _friendly_conn_error(e, host, port, engine):
45
+ """Turn a raw driver/socket exception into an actionable message. The most
46
+ common failure is a copy-pasted EXAMPLE DSN (host == 'host') or a Docker/VPC
47
+ hostname that doesn't resolve from the runner's machine."""
48
+ import socket
49
+ msg = str(e) or e.__class__.__name__
50
+ low = msg.lower()
51
+ host = host or ""
52
+ is_dns = isinstance(e, socket.gaierror) or "nodename nor servname" in low \
53
+ or "name or service not known" in low or "getaddrinfo" in low \
54
+ or "name does not resolve" in low or "no address associated" in low
55
+ if is_dns:
56
+ if host.lower() in _PLACEHOLDER_HOSTS:
57
+ return (f"the host is literally \"{host}\" - that's the EXAMPLE DSN, not your database. "
58
+ f"Paste your real connection string with the actual host, e.g. an AWS RDS / Cloud SQL "
59
+ f"endpoint: {engine.lower()}://user:pass@your-db.abc123.us-east-1.rds.amazonaws.com:{port}/db.")
60
+ if host and "." not in host and host not in ("localhost",):
61
+ return (f"can't resolve host \"{host}\" from the runner's machine. A bare name like this is "
62
+ f"usually a Docker/compose service or a VPC-internal alias that only resolves elsewhere - "
63
+ f"use the host that's reachable from this machine (e.g. your RDS/Cloud SQL endpoint, or a VPN/bastion host).")
64
+ return (f"can't resolve host \"{host}\" from the runner's machine - check the host in your DSN, and that "
65
+ f"this machine can reach it (DNS + network route; for a private RDS/VPC DB the runner must sit in the VPC or on an allow-listed IP).")
66
+ if isinstance(e, ConnectionRefusedError) or "refused" in low:
67
+ return f"host \"{host}\" reachable but port {port} refused the connection - is {engine} listening there / bound to that interface?"
68
+ if isinstance(e, (TimeoutError,)) or "timed out" in low or "timeout" in low:
69
+ return f"timed out connecting to {host}:{port} - firewall, IP allow-list, or wrong host/port?"
70
+ if "password authentication failed" in low or "authentication failed" in low or "access denied" in low:
71
+ return "authentication failed - check the username/password in the DSN."
72
+ if 'database "' in low and "does not exist" in low or "unknown database" in low:
73
+ return f"connected, but the database name in the DSN doesn't exist: {msg}"
74
+ return msg
75
+
76
+
77
+ # ── PostgreSQL (asyncpg) ─────────────────────────────────────────────────────
78
+ def probe_postgres(creds):
79
+ dsn = _pick(creds, "dsn", "POSTGRES_DSN")
80
+ if not dsn:
81
+ _out(False, "Connection DSN required")
82
+ try:
83
+ import asyncio
84
+ import asyncpg # type: ignore
85
+ except ImportError:
86
+ _out(False, "asyncpg not installed on this runner (pip install asyncpg)")
87
+ p = urllib.parse.urlparse(dsn)
88
+ host, port = (p.hostname or ""), (p.port or 5432)
89
+ if not host:
90
+ _out(False, "PostgreSQL connection failed: no host in the DSN - expected postgresql://user:pass@HOST:5432/db")
91
+
92
+ async def _run():
93
+ conn = await asyncpg.connect(dsn, timeout=10)
94
+ try:
95
+ await conn.fetchval("SELECT 1")
96
+ ver = await conn.fetchval("SELECT version()")
97
+ finally:
98
+ await conn.close()
99
+ return ver or ""
100
+
101
+ try:
102
+ ver = asyncio.run(asyncio.wait_for(_run(), timeout=15))
103
+ short = str(ver).split(" on ")[0] if ver else ""
104
+ _out(True, f"Connected to PostgreSQL{(' - ' + short) if short else ''}")
105
+ except Exception as e: # noqa: BLE001
106
+ _out(False, f"PostgreSQL connection failed: {_friendly_conn_error(e, host, port, 'PostgreSQL')}")
107
+
108
+
109
+ # ── MySQL / MariaDB (PyMySQL - pure-python, sync) ────────────────────────────
110
+ def probe_mysql(creds):
111
+ dsn = _pick(creds, "dsn", "MYSQL_DSN")
112
+ if not dsn:
113
+ _out(False, "Connection DSN required")
114
+ try:
115
+ import pymysql # type: ignore
116
+ except ImportError:
117
+ _out(False, "PyMySQL not installed on this runner (pip install PyMySQL)")
118
+ p = urllib.parse.urlparse(dsn)
119
+ host, port = (p.hostname or ""), (p.port or 3306)
120
+ if not host:
121
+ _out(False, "MySQL connection failed: no host in the DSN - expected mysql://user:pass@HOST:3306/db")
122
+ try:
123
+ conn = pymysql.connect(
124
+ host=host,
125
+ port=port,
126
+ user=urllib.parse.unquote(p.username or ""),
127
+ password=urllib.parse.unquote(p.password or ""),
128
+ db=(p.path or "").lstrip("/") or None,
129
+ connect_timeout=10,
130
+ read_timeout=10,
131
+ )
132
+ try:
133
+ with conn.cursor() as cur:
134
+ cur.execute("SELECT VERSION()")
135
+ row = cur.fetchone()
136
+ ver = (row[0] if row else "") or ""
137
+ finally:
138
+ conn.close()
139
+ _out(True, f"Connected to MySQL{(' - ' + str(ver)) if ver else ''}")
140
+ except Exception as e: # noqa: BLE001
141
+ _out(False, f"MySQL connection failed: {_friendly_conn_error(e, host, port, 'MySQL')}")
142
+
143
+
144
+ # ── Snowflake (key-pair JWT, mirrors the server handler) ─────────────────────
145
+ def probe_snowflake(creds):
146
+ account = _pick(creds, "account", "SNOWFLAKE_ACCOUNT")
147
+ user = _pick(creds, "user", "SNOWFLAKE_USER")
148
+ pk = _pick(creds, "private_key", "SNOWFLAKE_PRIVATE_KEY")
149
+ if not account or not user or not pk:
150
+ _out(False, "Account + Username + Private Key required")
151
+ try:
152
+ from cryptography.hazmat.primitives import serialization # type: ignore
153
+ import requests # type: ignore
154
+ except ImportError:
155
+ _out(False, "cryptography/requests not installed on this runner")
156
+
157
+ def b64url(b):
158
+ if isinstance(b, str):
159
+ b = b.encode()
160
+ return base64.urlsafe_b64encode(b).rstrip(b"=").decode()
161
+
162
+ try:
163
+ import time
164
+ key = serialization.load_pem_private_key(pk.encode(), password=None)
165
+ der_spki = key.public_key().public_bytes(
166
+ serialization.Encoding.DER,
167
+ serialization.PublicFormat.SubjectPublicKeyInfo,
168
+ )
169
+ fp = "SHA256:" + base64.b64encode(hashlib.sha256(der_spki).digest()).decode()
170
+ acct_upper = account.upper()
171
+ if "." in acct_upper:
172
+ acct_upper = acct_upper.split(".")[0]
173
+ qual = f"{acct_upper}.{user.upper()}"
174
+ now = int(time.time())
175
+ header = b64url(json.dumps({"alg": "RS256", "typ": "JWT"}))
176
+ payload = b64url(json.dumps(
177
+ {"iss": f"{qual}.{fp}", "sub": qual, "iat": now, "exp": now + 3540}))
178
+ signing_input = f"{header}.{payload}".encode()
179
+ from cryptography.hazmat.primitives import hashes # type: ignore
180
+ from cryptography.hazmat.primitives.asymmetric import padding # type: ignore
181
+ sig = key.sign(signing_input, padding.PKCS1v15(), hashes.SHA256())
182
+ jwt = f"{header}.{payload}.{b64url(sig)}"
183
+ host = f"https://{account.replace('_', '-').lower()}.snowflakecomputing.com"
184
+ r = requests.post(
185
+ f"{host}/api/v2/statements",
186
+ headers={
187
+ "Authorization": f"Bearer {jwt}",
188
+ "X-Snowflake-Authorization-Token-Type": "KEYPAIR_JWT",
189
+ "Content-Type": "application/json",
190
+ "Accept": "application/json",
191
+ },
192
+ json={"statement": "SELECT CURRENT_VERSION()", "timeout": 20},
193
+ timeout=15,
194
+ )
195
+ if r.ok:
196
+ v = ""
197
+ try:
198
+ v = (r.json().get("data") or [[None]])[0][0] or ""
199
+ except Exception: # noqa: BLE001
200
+ pass
201
+ _out(True, f"Connected to Snowflake{(' (v' + str(v) + ')') if v else ''}")
202
+ _out(False, f"Snowflake auth/query failed: HTTP {r.status_code} {r.text[:160]}")
203
+ except Exception as e: # noqa: BLE001
204
+ _out(False, f"Snowflake connection failed: {e}")
205
+
206
+
207
+ # ── Databricks (PAT or OAuth M2M, mirrors the server handler) ────────────────
208
208
  def probe_databricks(creds):
209
209
  import re
210
210
  from urllib.parse import urlsplit
211
211
  host = _pick(creds, "workspace_url", "DATABRICKS_HOST").rstrip("/")
212
- pat = _pick(creds, "token", "DATABRICKS_TOKEN")
213
- cid = _pick(creds, "client_id", "DATABRICKS_CLIENT_ID")
214
- sec = _pick(creds, "client_secret", "DATABRICKS_CLIENT_SECRET")
212
+ pat = _pick(creds, "token", "DATABRICKS_TOKEN")
213
+ cid = _pick(creds, "client_id", "DATABRICKS_CLIENT_ID")
214
+ sec = _pick(creds, "client_secret", "DATABRICKS_CLIENT_SECRET")
215
215
  if not host:
216
216
  _out(False, "Workspace URL required")
217
217
  url = urlsplit(host)
@@ -219,85 +219,85 @@ def probe_databricks(creds):
219
219
  or not re.fullmatch(r"(?!accounts(?:\.|-))[a-z0-9-]+(?:\.[a-z0-9-]+)*\.(?:cloud\.databricks\.com|azuredatabricks\.net|gcp\.databricks\.com)", url.hostname or "")):
220
220
  _out(False, "Use an HTTPS Databricks workspace URL without a path or query")
221
221
  telemetry = {"User-Agent": "MelayaLabs_Melaya/2.0.0"}
222
- try:
223
- import requests # type: ignore
224
- except ImportError:
225
- _out(False, "requests not installed on this runner")
226
- try:
227
- bearer = pat
228
- if not bearer:
229
- if not cid or not sec:
230
- _out(False, "Provide a Personal Access Token, or Client ID + Client Secret")
231
- tr = requests.post(
232
- f"{host}/oidc/v1/token",
233
- auth=(cid, sec),
222
+ try:
223
+ import requests # type: ignore
224
+ except ImportError:
225
+ _out(False, "requests not installed on this runner")
226
+ try:
227
+ bearer = pat
228
+ if not bearer:
229
+ if not cid or not sec:
230
+ _out(False, "Provide a Personal Access Token, or Client ID + Client Secret")
231
+ tr = requests.post(
232
+ f"{host}/oidc/v1/token",
233
+ auth=(cid, sec),
234
234
  data={"grant_type": "client_credentials", "scope": "all-apis"},
235
235
  headers=telemetry, allow_redirects=False,
236
- timeout=12,
237
- )
238
- tj = {}
239
- try:
240
- tj = tr.json()
241
- except Exception: # noqa: BLE001
242
- pass
236
+ timeout=12,
237
+ )
238
+ tj = {}
239
+ try:
240
+ tj = tr.json()
241
+ except Exception: # noqa: BLE001
242
+ pass
243
243
  if tr.status_code != 200 or not tj.get("access_token"):
244
244
  _out(False, f"Databricks OAuth failed: HTTP {tr.status_code}")
245
- bearer = tj["access_token"]
246
- r = requests.get(
247
- f"{host}/api/2.1/clusters/list?page_size=1",
245
+ bearer = tj["access_token"]
246
+ r = requests.get(
247
+ f"{host}/api/2.1/clusters/list?page_size=1",
248
248
  headers={"Authorization": f"Bearer {bearer}", **telemetry}, allow_redirects=False,
249
- timeout=12,
250
- )
249
+ timeout=12,
250
+ )
251
251
  if r.status_code == 200:
252
- _out(True, f"Connected to Databricks ({host.replace('https://', '').replace('http://', '')})")
253
- _out(False, f"Databricks unreachable: HTTP {r.status_code} {r.text[:120]}")
254
- except Exception as e: # noqa: BLE001
255
- _out(False, f"Databricks connection failed: {e}")
256
-
257
-
258
- # ── SQLite (local file on the runner) ────────────────────────────────────────
259
- def probe_sqlite(creds):
260
- import os
261
- import sqlite3
262
- path = _pick(creds, "dsn", "db_path", "path")
263
- if not path:
264
- _out(False, "Database file path required")
265
- if not os.path.exists(path):
266
- _out(False, f"No SQLite file at {path}")
267
- try:
268
- conn = sqlite3.connect(path, timeout=8)
269
- try:
270
- conn.execute("SELECT 1")
271
- finally:
272
- conn.close()
273
- _out(True, f"Opened SQLite database ({os.path.basename(path)})")
274
- except Exception as e: # noqa: BLE001
275
- _out(False, f"SQLite open failed: {e}")
276
-
277
-
278
- _PROBES = {
279
- "postgres": probe_postgres,
280
- "mysql": probe_mysql,
281
- "snowflake": probe_snowflake,
282
- "databricks": probe_databricks,
283
- "sqlite": probe_sqlite,
284
- }
285
-
286
-
287
- def main():
288
- try:
289
- job = json.loads(sys.stdin.read() or "{}")
290
- except Exception as e: # noqa: BLE001
291
- _out(False, f"bad probe job: {e}")
292
- return
293
- service = (job.get("service") or "").strip()
294
- creds = job.get("creds") or {}
295
- fn = _PROBES.get(service)
296
- if not fn:
297
- _out(False, f"unsupported service: {service}")
298
- return
299
- fn(creds)
300
-
301
-
302
- if __name__ == "__main__":
303
- main()
252
+ _out(True, f"Connected to Databricks ({host.replace('https://', '').replace('http://', '')})")
253
+ _out(False, f"Databricks unreachable: HTTP {r.status_code} {r.text[:120]}")
254
+ except Exception as e: # noqa: BLE001
255
+ _out(False, f"Databricks connection failed: {e}")
256
+
257
+
258
+ # ── SQLite (local file on the runner) ────────────────────────────────────────
259
+ def probe_sqlite(creds):
260
+ import os
261
+ import sqlite3
262
+ path = _pick(creds, "dsn", "db_path", "path")
263
+ if not path:
264
+ _out(False, "Database file path required")
265
+ if not os.path.exists(path):
266
+ _out(False, f"No SQLite file at {path}")
267
+ try:
268
+ conn = sqlite3.connect(path, timeout=8)
269
+ try:
270
+ conn.execute("SELECT 1")
271
+ finally:
272
+ conn.close()
273
+ _out(True, f"Opened SQLite database ({os.path.basename(path)})")
274
+ except Exception as e: # noqa: BLE001
275
+ _out(False, f"SQLite open failed: {e}")
276
+
277
+
278
+ _PROBES = {
279
+ "postgres": probe_postgres,
280
+ "mysql": probe_mysql,
281
+ "snowflake": probe_snowflake,
282
+ "databricks": probe_databricks,
283
+ "sqlite": probe_sqlite,
284
+ }
285
+
286
+
287
+ def main():
288
+ try:
289
+ job = json.loads(sys.stdin.read() or "{}")
290
+ except Exception as e: # noqa: BLE001
291
+ _out(False, f"bad probe job: {e}")
292
+ return
293
+ service = (job.get("service") or "").strip()
294
+ creds = job.get("creds") or {}
295
+ fn = _PROBES.get(service)
296
+ if not fn:
297
+ _out(False, f"unsupported service: {service}")
298
+ return
299
+ fn(creds)
300
+
301
+
302
+ if __name__ == "__main__":
303
+ main()