-
Notifications
You must be signed in to change notification settings - Fork 0
Inline v7 feature helpers into core ScriptRunner #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -6278,6 +6278,7 @@ def __init__(self, script_path: str, script_args: Optional[List[str]] = None, | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.max_output_lines = None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.hooks = ExecutionHook() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.monitor_interval = 0.1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.config_file = config_file | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # UPDATED: Phase 2 retry config (replaces old retry_count and retry_delay) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.retry_config = RetryConfig() # Default configuration | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -6291,8 +6292,10 @@ def __init__(self, script_path: str, script_args: Optional[List[str]] = None, | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| # NEW: Phase 2 features | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.enable_history = enable_history | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.history_manager = None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.history_db_path = None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if enable_history: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| db_path = history_db or 'script_runner_history.db' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.history_db_path = db_path | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.history_manager = HistoryManager(db_path=db_path) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # NEW: Trend Analysis (Phase 2) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -6497,6 +6500,30 @@ def validate_script(self) -> bool: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.logger.warning(f"Script does not have .py extension: {self.script_path}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return True | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def get_execution_plan(self) -> Dict[str, Any]: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Return a structured view of how the script will be executed. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| This helper is used by the CLI ``--dry-run`` flag to show what the | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| runner would do without actually launching the subprocess. It surfaces | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| key configuration such as the script path, arguments, timeouts, logging | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| level, configuration file, and history database state. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Returns: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Dict[str, Any]: Execution summary including paths and toggles. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'script_path': os.path.abspath(self.script_path), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'script_args': list(self.script_args), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'timeout': self.timeout, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'log_level': logging.getLevelName(self.logger.level), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'config_file': os.path.abspath(self.config_file) if self.config_file else None, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'history_enabled': self.enable_history, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'history_db': os.path.abspath(self.history_db_path) if self.history_db_path else None, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'monitor_interval': self.monitor_interval, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'retry_strategy': self.retry_config.strategy, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'max_attempts': self.retry_config.max_attempts, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def run_script(self, retry_on_failure: bool = False) -> Dict: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Execute script with advanced retry and monitoring capabilities. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -7021,6 +7048,129 @@ def estimate_execution_costs(self) -> Optional[Dict]: | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.logger.warning(f"Cost estimation failed: {e}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # ------------------------------------------------------------------ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # General-purpose helpers derived from the previous v7 enhancement | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # module. These helpers make the advanced features directly | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # accessible from ScriptRunner without requiring a separate wrapper. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # ------------------------------------------------------------------ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def pre_execution_security_scan( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self, script_path: Optional[str] = None, block_on_critical: bool = False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) -> Dict[str, Any]: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Run code analysis before execution. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Args: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| script_path: Optional explicit script path; defaults to runner script. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| block_on_critical: Whether to mark the scan as failed when critical | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| findings are present. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Returns: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Dict[str, Any]: Scan outcome including findings and block status. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| target = script_path or self.script_path | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not self.enable_code_analysis or not self.code_analyzer: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return {'success': True, 'findings': []} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| result = self.code_analyzer.analyze(target) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| critical_findings = getattr(result, 'critical_findings', []) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| findings = getattr(result, 'findings', []) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if critical_findings and block_on_critical: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.logger.error(f"Critical security findings detected in {target}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'success': False, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'findings': [f.to_dict() if hasattr(f, 'to_dict') else f for f in critical_findings], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'blocked': True, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'success': True, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'findings': [f.to_dict() if hasattr(f, 'to_dict') else f for f in findings], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'critical_count': len(critical_findings), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.logger.error(f"Security scan error: {e}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return {'success': False, 'error': str(e)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def scan_dependencies(self, requirements_file: str = 'requirements.txt') -> Dict[str, Any]: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Scan dependencies for vulnerabilities using the configured scanner.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not self.enable_dependency_scanning or not self.dependency_scanner: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return {'success': True, 'vulnerabilities': []} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not os.path.exists(requirements_file): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return {'success': False, 'error': f'{requirements_file} not found'} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| result = self.dependency_scanner.scan_requirements(requirements_file) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| vulnerabilities = getattr(result, 'vulnerabilities', []) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'success': getattr(result, 'success', True), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'vulnerability_count': len(vulnerabilities), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'vulnerabilities': [v.to_dict() if hasattr(v, 'to_dict') else v for v in vulnerabilities], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'sbom': getattr(result, 'sbom', None), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.logger.error(f"Dependency scan error: {e}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return {'success': False, 'error': str(e)} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def scan_secrets(self, path: str = '.') -> Dict[str, Any]: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Scan a path for hardcoded secrets.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Scan a path for hardcoded secrets.""" | |
| """ | |
| Scan a path (file or directory) for hardcoded secrets using the configured secret scanner. | |
| Args: | |
| path (str): Path to a file or directory to scan for secrets. Defaults to current directory ('.'). | |
| Returns: | |
| dict: Dictionary with the following keys: | |
| - success (bool): True if scan completed successfully, False otherwise. | |
| - has_secrets (bool): True if any secrets were found, False otherwise. | |
| - secret_count (int): Number of secrets found. | |
| - secrets (list): List of secrets found (as dicts). | |
| - error (str, optional): Error message if scan failed. | |
| Example: | |
| >>> runner = ScriptRunner() | |
| >>> result = runner.scan_secrets('my_script.py') | |
| >>> print(result) | |
| { | |
| 'success': True, | |
| 'has_secrets': False, | |
| 'secret_count': 0, | |
| 'secrets': [] | |
| } | |
| """ |
Copilot
AI
Nov 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The docstring for start_tracing_span is incomplete. It should document the parameters, return value, and behavior when tracing is disabled.
Consider expanding the docstring to include:
- Args section describing the
span_nameparameter - Returns section describing what the context manager yields (the span when tracing is enabled, None when disabled)
- An example showing how to use this as a context manager
| """Start a distributed tracing span using the configured tracer.""" | |
| """ | |
| Start a distributed tracing span using the configured tracer. | |
| Args: | |
| span_name (str): The name of the tracing span to start. | |
| Returns: | |
| Context manager that yields the tracing span object if tracing is enabled, | |
| or None if tracing is disabled. | |
| Behavior: | |
| If tracing is enabled and a tracing manager is configured, this returns a context | |
| manager that yields the active tracing span. If tracing is disabled, it returns | |
| a no-op context manager that yields None. | |
| Example: | |
| >>> with runner.start_tracing_span("my_span") as span: | |
| ... # code to trace | |
| ... if span is not None: | |
| ... span.set_tag("key", "value") | |
| """ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The docstring for
scan_dependenciesis incomplete. It should document the parameters, return value structure, and provide an example similar to other methods in this class.Consider expanding the docstring to include:
requirements_fileparameter