Qualitätsprüfungen und Fehlerpfade härten
Some checks failed
Tests / test (push) Has been cancelled

This commit is contained in:
2026-10-15 10:05:48 +02:00
parent c0498b6935
commit 9dc37d1ece
12 changed files with 2383 additions and 517 deletions

View File

@@ -3,6 +3,7 @@
Stdlib only; server assigns numbers and immutable reports stay unchanged.
"""
import argparse
import contextlib
import datetime as dt
import hashlib
import time
@@ -116,7 +117,8 @@ def credentials(config):
base = config.get('url', '')
if not base: raise ValueError('Inventory URL is not configured; reports remain offline')
parsed = urllib.parse.urlparse(base)
if parsed.scheme != 'https' or not parsed.hostname or parsed.username or parsed.password or parsed.query or parsed.fragment:
if (parsed.scheme != 'https' or not parsed.hostname or parsed.username or parsed.password
or parsed.path not in ('', '/') or parsed.params or parsed.query or parsed.fragment):
raise ValueError('Inventory URL must be an HTTPS origin (e.g. https://inventory.example.org)')
key = os.environ.get('CSR_API_KEY', '')
if not key:
@@ -124,7 +126,7 @@ def credentials(config):
key = secret.read_text(encoding='utf-8').strip()
if not key:
raise ValueError('No API key configured')
return base, key
return base.rstrip('/'), key
def connection_error(ex):
# Never include server responses, request headers, or raw URLs in errors.
@@ -252,6 +254,8 @@ def process_queue(root, db, config, retry=False, selected=None, retry_seconds=60
failures += 1
# Error strings deliberately exclude URLs, API keys and raw server responses.
error = connection_error(ex)
if isinstance(ex, urllib.error.HTTPError):
ex.close()
delay = min(3600, retry_seconds * (2 ** min(attempts, 6)))
with db:
db.execute("UPDATE inventory_queue SET status='ERROR', error=?, next_attempt=? WHERE report_sha256=?", (error, stamp + delay, sha))
@@ -347,7 +351,7 @@ def run_station(root,action,report_path):
'hwid':hardware_id(report),'expected_revision':device['revision'],'specifications':map_fields(report)})
if action == 'intake' and report_path:
raw=report_path.read_bytes(); sha=hashlib.sha256(raw).hexdigest()
with sqlite3.connect(root/'database/intake.sqlite3') as db:
with contextlib.closing(sqlite3.connect(root/'database/intake.sqlite3')) as db, db:
init_db(db);discover_queue(root,db)
db.execute("INSERT OR REPLACE INTO inventory_sync(report_sha256,asset_id,inventory_number,status,last_attempt_utc,response) VALUES(?,?,?,'SYNCED',?,'OK')",(sha,report.get('asset_id',''),device['number'],dt.datetime.now(dt.timezone.utc).isoformat()))
db.execute("UPDATE inventory_queue SET status='SYNCED',error='',next_attempt=0 WHERE report_sha256=?",(sha,))
@@ -388,10 +392,12 @@ def main():
return 2
if args.station:
try: return run_station(root,args.station,args.report)
except (OSError,ValueError,KeyError) as ex:
except (OSError,ValueError,KeyError,urllib.error.URLError) as ex:
print(connection_error(ex),file=sys.stderr)
if isinstance(ex, urllib.error.HTTPError):
ex.close()
return 2
with sqlite3.connect(root / 'database' / 'intake.sqlite3', timeout=20) as db:
with contextlib.closing(sqlite3.connect(root / 'database' / 'intake.sqlite3', timeout=20)) as db, db:
init_db(db)
discover_queue(root, db)
if args.list: