This commit is contained in:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user