Initial server source import
This commit is contained in:
@@ -0,0 +1,447 @@
|
||||
import ast
|
||||
import inspect
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from types import SimpleNamespace
|
||||
from unittest import mock
|
||||
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
APP = os.path.join(ROOT, 'app')
|
||||
if APP not in sys.path:
|
||||
sys.path.insert(0, APP)
|
||||
|
||||
import console_runner
|
||||
from scanner_db import DiscoveryPausedError
|
||||
|
||||
|
||||
class DiscoveryDB:
|
||||
def __init__(self, *, paused=False, enqueue_error=None):
|
||||
self.conn = SimpleNamespace(is_postgres=True)
|
||||
self.paused = paused
|
||||
self.enqueue_error = enqueue_error
|
||||
self.enqueues = []
|
||||
self.observations = []
|
||||
self.finished = []
|
||||
|
||||
def require_runtime_safety_schema(self):
|
||||
return True
|
||||
|
||||
def require_final_cutover(self):
|
||||
return True
|
||||
|
||||
def runtime_control_state(self):
|
||||
return {'effective_discovery_paused': self.paused}
|
||||
|
||||
def known_target_normalizations_for(self, source, platform, targets):
|
||||
return set()
|
||||
|
||||
def enqueue_targets(
|
||||
self, source, platform, query, targets, requeue_done=False,
|
||||
unresolved_targets=None, *, discovery_admission=False,
|
||||
):
|
||||
if self.enqueue_error is not None:
|
||||
raise self.enqueue_error
|
||||
record = {
|
||||
'source': source,
|
||||
'platform': platform,
|
||||
'query': query,
|
||||
'targets': list(targets),
|
||||
'unresolved_targets': list(unresolved_targets or []),
|
||||
'discovery_admission': discovery_admission,
|
||||
}
|
||||
self.enqueues.append(record)
|
||||
return len(record['targets']) + len(record['unresolved_targets'])
|
||||
|
||||
def observe_discovered_targets(
|
||||
self, source, platform, query, discoveries, *, rescan_limit,
|
||||
cooldown_seconds,
|
||||
):
|
||||
self.observations.append({
|
||||
'source': source,
|
||||
'platform': platform,
|
||||
'query': query,
|
||||
'discoveries': list(discoveries),
|
||||
'rescan_limit': rescan_limit,
|
||||
'cooldown_seconds': cooldown_seconds,
|
||||
})
|
||||
return {
|
||||
'attempted_count': len(discoveries),
|
||||
'queued_new_count': len(discoveries),
|
||||
'queued_updated_count': 0,
|
||||
}
|
||||
|
||||
def finish_source_cycle(self, *args):
|
||||
self.finished.append(args)
|
||||
return True
|
||||
|
||||
def has_claimable_targets(self, *args, **kwargs):
|
||||
raise AssertionError('discovery-only cycle probed scanner backlog')
|
||||
|
||||
def has_claimable_targets_v2(self, *args, **kwargs):
|
||||
raise AssertionError('discovery-only cycle probed scanner backlog')
|
||||
|
||||
|
||||
def args_for(source):
|
||||
platforms = {
|
||||
'gitlab': 'gitlab',
|
||||
'dockerhub': 'docker',
|
||||
'huggingface': 'huggingface',
|
||||
}
|
||||
return SimpleNamespace(
|
||||
platform=platforms[source],
|
||||
query='credential',
|
||||
updated_target_rescan_enabled=False,
|
||||
)
|
||||
|
||||
|
||||
class DiscoveryOnlyCycleTests(unittest.TestCase):
|
||||
def test_discovery_producer_mode_uses_only_discovery_runner_and_postgres(self):
|
||||
class ProducerDB:
|
||||
enabled = True
|
||||
conn = SimpleNamespace(is_postgres=True)
|
||||
|
||||
def __init__(self):
|
||||
self.application_name = None
|
||||
self.started = None
|
||||
self.finished = []
|
||||
self.closed = False
|
||||
|
||||
def set_application_name(self, value):
|
||||
self.application_name = value
|
||||
|
||||
def require_runtime_safety_schema(self):
|
||||
return True
|
||||
|
||||
def require_final_cutover(self):
|
||||
return True
|
||||
|
||||
def start_run(self, *args, **kwargs):
|
||||
self.started = (args, kwargs)
|
||||
return 41
|
||||
|
||||
def finish_run(self, *args):
|
||||
self.finished.append(args)
|
||||
|
||||
def close(self):
|
||||
self.closed = True
|
||||
|
||||
database = ProducerDB()
|
||||
state = {'sources': {'gitlab': console_runner.default_source_state()}}
|
||||
config = {
|
||||
'global': {'database_url': 'postgresql://managed'},
|
||||
'sources': {'gitlab': {'enabled': True}},
|
||||
}
|
||||
args = SimpleNamespace(
|
||||
config='config.yaml', source='gitlab', once=True,
|
||||
show_state=False, cleanup_only=False,
|
||||
)
|
||||
with mock.patch.object(console_runner, 'preflight_lifecycle_paths') as preflight, \
|
||||
mock.patch.object(console_runner, 'apply_global_config'), \
|
||||
mock.patch.object(console_runner, 'require_sensitive_runtime_paths'), \
|
||||
mock.patch.object(console_runner, 'get_state_path', return_value='state.json'), \
|
||||
mock.patch.object(console_runner, 'load_state', return_value=state), \
|
||||
mock.patch.object(console_runner, 'load_secrets', return_value={}), \
|
||||
mock.patch.object(console_runner, 'refresh_auth_summary'), \
|
||||
mock.patch.object(console_runner, 'save_state'), \
|
||||
mock.patch.object(console_runner, 'hash_file', return_value='a' * 64), \
|
||||
mock.patch.object(console_runner, 'ScannerDB', return_value=database), \
|
||||
mock.patch.object(console_runner, 'run_configured_source', return_value={}) as configured, \
|
||||
mock.patch.object(console_runner, 'initialize_scanner_runtime') as scanner_init, \
|
||||
mock.patch.object(console_runner, 'check_dependencies') as dependencies, \
|
||||
mock.patch.object(console_runner, 'cleanup_pending_command_work_dirs') as cleanup_work, \
|
||||
mock.patch.object(console_runner, 'cleanup_stale_temp_dirs') as cleanup_temp:
|
||||
console_runner.run_discovery_producer_mode(args, config)
|
||||
|
||||
preflight.assert_called_once_with(
|
||||
'config.yaml', config, authority_profile='discovery-producer',
|
||||
)
|
||||
scanner_init.assert_not_called()
|
||||
dependencies.assert_not_called()
|
||||
cleanup_work.assert_not_called()
|
||||
cleanup_temp.assert_not_called()
|
||||
self.assertEqual(database.application_name, 'truf-discovery:gitlab')
|
||||
self.assertEqual(database.started[0][0], 'discovery-producer')
|
||||
self.assertEqual(database.started[1]['selected_source'], 'gitlab')
|
||||
self.assertEqual(database.started[1]['selected_platform'], 'gitlab')
|
||||
self.assertTrue(database.closed)
|
||||
configured.assert_called_once()
|
||||
self.assertIs(configured.call_args.kwargs['cycle_runner'], console_runner.run_discovery_cycle)
|
||||
|
||||
def test_discovery_producer_mode_requires_exact_one_shot_core_source(self):
|
||||
config = {'global': {}, 'sources': {
|
||||
'gitlab': {}, 'dockerhub': {}, 'huggingface': {}, 'github': {}, 'docker': {},
|
||||
}}
|
||||
cases = (
|
||||
(False, 'gitlab'),
|
||||
(True, None),
|
||||
(True, 'github'),
|
||||
(True, 'docker'),
|
||||
)
|
||||
for once, source in cases:
|
||||
args = SimpleNamespace(
|
||||
config='config.yaml', source=source, once=once,
|
||||
show_state=False, cleanup_only=False,
|
||||
)
|
||||
with self.subTest(once=once, source=source), \
|
||||
mock.patch.object(console_runner, 'preflight_lifecycle_paths') as preflight, \
|
||||
self.assertRaises(SystemExit):
|
||||
console_runner.run_discovery_producer_mode(args, config)
|
||||
preflight.assert_not_called()
|
||||
|
||||
def run_cycle(self, source, fetched, *, database=None):
|
||||
database = database or DiscoveryDB()
|
||||
with mock.patch.object(
|
||||
console_runner, 'fetch_targets', return_value=fetched,
|
||||
) as fetch:
|
||||
metrics = console_runner.run_discovery_cycle(
|
||||
args_for(source), database, 11, 17, source,
|
||||
)
|
||||
fetch.assert_called_once()
|
||||
return database, metrics
|
||||
|
||||
def assert_zero_scan_metrics(self, metrics):
|
||||
self.assertEqual(metrics['scan_requested_count'], 0)
|
||||
self.assertEqual(metrics['scanned_count'], 0)
|
||||
self.assertEqual(metrics['staged_count'], 0)
|
||||
self.assertEqual(metrics['findings_count'], 0)
|
||||
self.assertFalse(metrics['backlog_only'])
|
||||
|
||||
def test_gitlab_discovery_normalizes_and_uses_provider_admission(self):
|
||||
database, metrics = self.run_cycle(
|
||||
'gitlab',
|
||||
['https://gitlab.com/acme/one.git', 'https://gitlab.com/acme/two'],
|
||||
)
|
||||
self.assertEqual(metrics['cycle_status'], 'completed')
|
||||
self.assertEqual(metrics['fetched_count'], 2)
|
||||
self.assertEqual(metrics['queued_new_count'], 2)
|
||||
self.assertEqual(len(database.enqueues), 1)
|
||||
self.assertTrue(database.enqueues[0]['discovery_admission'])
|
||||
self.assertEqual(database.enqueues[0]['source'], 'gitlab')
|
||||
self.assertEqual(len(database.finished), 1)
|
||||
self.assert_zero_scan_metrics(metrics)
|
||||
|
||||
def test_huggingface_discovery_preserves_newest_remote_metadata(self):
|
||||
database = DiscoveryDB()
|
||||
args = args_for('huggingface')
|
||||
args.updated_target_rescan_enabled = True
|
||||
args.updated_target_rescan_max_per_cycle = 4
|
||||
args.updated_target_rescan_cooldown_hours = 12
|
||||
fetched = [
|
||||
{'target': 'acme/space', 'remote_modified_at': '2026-09-18T00:00:00Z'},
|
||||
{'target': 'acme/space', 'remote_modified_at': '2026-09-19T00:00:00Z'},
|
||||
]
|
||||
with mock.patch.object(
|
||||
console_runner, 'fetch_targets', return_value=fetched,
|
||||
):
|
||||
metrics = console_runner.run_discovery_cycle(
|
||||
args, database, 11, 17, 'huggingface',
|
||||
)
|
||||
self.assertEqual(database.observations[0]['discoveries'], [{
|
||||
'target': 'acme/space',
|
||||
'remote_modified_at': '2026-09-19T00:00:00+00:00',
|
||||
}])
|
||||
self.assertEqual(database.observations[0]['rescan_limit'], 4)
|
||||
self.assertEqual(database.observations[0]['cooldown_seconds'], 12 * 3600)
|
||||
self.assertEqual(metrics['queued_new_count'], 1)
|
||||
self.assert_zero_scan_metrics(metrics)
|
||||
|
||||
def test_huggingface_discovery_filters_explicit_restrictions(self):
|
||||
database = DiscoveryDB()
|
||||
args = args_for('huggingface')
|
||||
args.updated_target_rescan_enabled = True
|
||||
args.updated_target_rescan_max_per_cycle = 4
|
||||
args.updated_target_rescan_cooldown_hours = 12
|
||||
records = [
|
||||
{'target': 'acme/public', 'private': False, 'gated': None},
|
||||
{'target': 'acme/metadata-omitted'},
|
||||
{'target': 'acme/private', 'private': True},
|
||||
{'target': 'acme/protected', 'protected': True},
|
||||
{'target': 'acme/gated', 'gated': 'manual'},
|
||||
{'target': 'acme/disabled', 'disabled': True},
|
||||
{'target': 'acme/malformed', 'private': 'false'},
|
||||
]
|
||||
projected, _metrics = console_runner.enqueue_discovered_targets(
|
||||
args, records, database, 11, 17, 'huggingface',
|
||||
)
|
||||
self.assertEqual(projected, ['acme/public', 'acme/metadata-omitted'])
|
||||
self.assertEqual(
|
||||
[item['target'] for item in database.observations[0]['discoveries']],
|
||||
['acme/public', 'acme/metadata-omitted'],
|
||||
)
|
||||
|
||||
def test_huggingface_fetch_uses_existing_metadata_without_second_request(self):
|
||||
args = SimpleNamespace(
|
||||
platform='huggingface', mode='search', token='discovery-token',
|
||||
pages=1, fetch_timeout=15, updated_target_rescan_enabled=False,
|
||||
stop_on_seen_pages=False, huggingface_discovery_request_attempts=1,
|
||||
huggingface_discovery_retry_delay=0,
|
||||
)
|
||||
spaces = [
|
||||
{'url': 'acme/public', 'updated_at': '2026-09-19T00:00:00Z', 'private': False},
|
||||
{'url': 'acme/private', 'updated_at': '2026-09-19T00:00:00Z', 'private': True},
|
||||
{'url': 'acme/protected', 'protected': True},
|
||||
{'url': 'acme/gated', 'gated': 'manual'},
|
||||
{'url': 'acme/disabled', 'disabled': True},
|
||||
{'url': 'acme/metadata-omitted'},
|
||||
]
|
||||
with mock.patch.object(
|
||||
console_runner, 'fetch_huggingface_spaces', return_value=spaces,
|
||||
) as fetch:
|
||||
targets = console_runner.fetch_targets(args)
|
||||
self.assertEqual(
|
||||
[item['target'] for item in targets],
|
||||
['acme/public', 'acme/metadata-omitted'],
|
||||
)
|
||||
self.assertFalse(targets[0]['private'])
|
||||
self.assertEqual(targets[0]['remote_modified_at'], '2026-09-19T00:00:00+00:00')
|
||||
self.assertTrue(fetch.call_args.kwargs['return_metadata'])
|
||||
|
||||
def test_dockerhub_discovery_enqueues_bare_repositories_as_unresolved(self):
|
||||
digest = 'library/busybox@sha256:' + ('a' * 64)
|
||||
database, metrics = self.run_cycle(
|
||||
'dockerhub', ['library/alpine', digest],
|
||||
)
|
||||
admission = database.enqueues[0]
|
||||
self.assertEqual(admission['targets'], [digest])
|
||||
self.assertEqual(admission['unresolved_targets'], ['library/alpine'])
|
||||
self.assertTrue(admission['discovery_admission'])
|
||||
self.assertEqual(metrics['queued_new_count'], 2)
|
||||
self.assert_zero_scan_metrics(metrics)
|
||||
|
||||
def test_every_core_producer_runs_with_backlog_and_zero_scan_work(self):
|
||||
discoveries = {
|
||||
'gitlab': ['https://gitlab.com/acme/project'],
|
||||
'dockerhub': ['library/alpine'],
|
||||
'huggingface': ['acme/space'],
|
||||
}
|
||||
for source, fetched in discoveries.items():
|
||||
with self.subTest(source=source), \
|
||||
mock.patch.object(console_runner, 'prepare_targets') as prepare, \
|
||||
mock.patch.object(console_runner, 'run_cycle_v2') as scanner_cycle, \
|
||||
mock.patch.object(console_runner, 'scan_target_result') as scan, \
|
||||
mock.patch.object(console_runner, 'stage_result_bundle') as stage:
|
||||
database, metrics = self.run_cycle(source, fetched)
|
||||
prepare.assert_not_called()
|
||||
scanner_cycle.assert_not_called()
|
||||
scan.assert_not_called()
|
||||
stage.assert_not_called()
|
||||
self.assertEqual(metrics['fetched_count'], 1)
|
||||
self.assertEqual(metrics['cycle_status'], 'completed')
|
||||
self.assertEqual(len(database.finished), 1)
|
||||
self.assert_zero_scan_metrics(metrics)
|
||||
|
||||
def test_persistent_pause_skips_provider_and_admission_for_every_core_source(self):
|
||||
for source in ('gitlab', 'dockerhub', 'huggingface'):
|
||||
with self.subTest(source=source):
|
||||
database = DiscoveryDB(paused=True)
|
||||
with mock.patch.object(console_runner, 'fetch_targets') as fetch:
|
||||
metrics = console_runner.run_discovery_cycle(
|
||||
args_for(source), database, 11, 17, source,
|
||||
)
|
||||
fetch.assert_not_called()
|
||||
self.assertEqual(database.enqueues, [])
|
||||
self.assertEqual(metrics['cycle_status'], 'paused')
|
||||
self.assertEqual(database.finished[0][1], 'paused')
|
||||
self.assert_zero_scan_metrics(metrics)
|
||||
|
||||
def test_pause_racing_admission_finishes_once_as_paused(self):
|
||||
control = {'effective_discovery_paused': True}
|
||||
database = DiscoveryDB(enqueue_error=DiscoveryPausedError(control))
|
||||
with mock.patch.object(
|
||||
console_runner, 'fetch_targets', return_value=['https://gitlab.com/acme/one'],
|
||||
):
|
||||
metrics = console_runner.run_discovery_cycle(
|
||||
args_for('gitlab'), database, 11, 17, 'gitlab',
|
||||
)
|
||||
self.assertEqual(metrics['cycle_status'], 'paused')
|
||||
self.assertEqual(metrics['queued_new_count'], 0)
|
||||
self.assertEqual(len(database.finished), 1)
|
||||
self.assert_zero_scan_metrics(metrics)
|
||||
|
||||
def test_empty_discovery_finishes_once_with_zero_scan_work(self):
|
||||
database, metrics = self.run_cycle('huggingface', [])
|
||||
self.assertEqual(len(database.finished), 1)
|
||||
self.assertEqual(database.finished[0][1], 'completed')
|
||||
self.assert_zero_scan_metrics(metrics)
|
||||
|
||||
def test_discovery_call_graph_has_no_scanner_claim_or_bundle_operations(self):
|
||||
forbidden = {
|
||||
'run_cycle', 'run_cycle_v2', '_run_cycle_legacy_compat',
|
||||
'prepare_targets', 'prepare_scan_options', 'acquire_scan_slot',
|
||||
'acquire_scan_slot_leases', 'scan_slot_scope', 'claim_targets',
|
||||
'reserve_and_claim_target', 'recover_result_reservation_claim',
|
||||
'has_claimable_targets', 'has_claimable_targets_v2',
|
||||
'resolve_due_docker_queue_targets_if_scan_queue_empty',
|
||||
'resolve_and_bind_git_claim',
|
||||
'resolve_and_bind_docker_claim', 'scan_target_result', 'scan_targets_batch',
|
||||
'result_spool_for_args', 'reserve_result_spool_claims',
|
||||
'prepare_scan_event', 'stage_scan_result_in_scope', 'stage_result_bundle',
|
||||
'ensure_bundle_reservation_paths', 'mark_result_bundle_ready',
|
||||
}
|
||||
names = set()
|
||||
for function in (
|
||||
console_runner.run_discovery_producer_mode,
|
||||
console_runner.run_discovery_cycle,
|
||||
console_runner.enqueue_discovered_targets,
|
||||
console_runner.run_dockerhub_incremental_discovery,
|
||||
console_runner.process_dockerhub_discovery_retry,
|
||||
console_runner.resolve_due_docker_queue_targets,
|
||||
console_runner.resolve_due_docker_experiment_targets,
|
||||
):
|
||||
tree = ast.parse(inspect.getsource(function))
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, ast.Call):
|
||||
target = node.func
|
||||
if isinstance(target, ast.Name):
|
||||
names.add(target.id)
|
||||
elif isinstance(target, ast.Attribute):
|
||||
names.add(target.attr)
|
||||
self.assertEqual(names & forbidden, set())
|
||||
|
||||
def test_configured_paused_cycle_does_not_advance_query(self):
|
||||
class ConfiguredDB:
|
||||
conn = SimpleNamespace(is_postgres=True)
|
||||
|
||||
def start_source_cycle(self, *args, **kwargs):
|
||||
return 17
|
||||
|
||||
config = {
|
||||
'global': {},
|
||||
'sources': {
|
||||
'gitlab': {'queries': ['first', 'second'], 'mode': 'recent'},
|
||||
},
|
||||
}
|
||||
state = {'sources': {'gitlab': console_runner.default_source_state()}}
|
||||
args = SimpleNamespace(platform='gitlab', mode='recent')
|
||||
runner = mock.Mock(return_value={
|
||||
'cycle_status': 'paused',
|
||||
'scan_requested_count': 0,
|
||||
'staged_count': 0,
|
||||
'scanned_count': 0,
|
||||
'source_failure_count': 0,
|
||||
})
|
||||
with tempfile.TemporaryDirectory() as temp_dir, \
|
||||
mock.patch.object(console_runner, 'save_state'), \
|
||||
mock.patch.object(console_runner, 'select_auth_entry', return_value=None), \
|
||||
mock.patch.object(console_runner, 'refresh_auth_summary'), \
|
||||
mock.patch.object(
|
||||
console_runner, 'build_args_from_source_config', return_value=args,
|
||||
), \
|
||||
mock.patch.object(console_runner, 'configure_source_auth'), \
|
||||
mock.patch.object(console_runner, 'queue_counts_for_args', return_value={}):
|
||||
metrics = console_runner.run_configured_source(
|
||||
'gitlab', config, state, os.path.join(temp_dir, 'state.json'), {},
|
||||
ConfiguredDB(), run_id=3, cycle_runner=runner,
|
||||
)
|
||||
self.assertEqual(metrics['cycle_status'], 'paused')
|
||||
self.assertEqual(state['sources']['gitlab']['query_index'], 0)
|
||||
self.assertEqual(state['sources']['gitlab']['last_status'], 'paused')
|
||||
self.assertEqual(state['sources']['gitlab']['last_scanned'], 0)
|
||||
runner.assert_called_once()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user