updates
This commit is contained in:
765
Backend/venv/lib/python3.12/site-packages/safety/init/command.py
Normal file
765
Backend/venv/lib/python3.12/site-packages/safety/init/command.py
Normal file
@@ -0,0 +1,765 @@
|
||||
import logging
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Optional,
|
||||
Iterator,
|
||||
)
|
||||
|
||||
from rich.live import Live
|
||||
from rich.padding import Padding
|
||||
from rich.panel import Panel
|
||||
from rich.prompt import Prompt
|
||||
from rich.syntax import Syntax
|
||||
from rich.table import Table
|
||||
from rich.text import Text
|
||||
|
||||
from safety.codebase_utils import load_unverified_project_from_config
|
||||
from safety.events.utils.emission import (
|
||||
emit_codebase_detection_status,
|
||||
emit_codebase_setup_completed,
|
||||
emit_init_exited,
|
||||
emit_init_scan_completed,
|
||||
)
|
||||
from safety.init.models import StepTracker
|
||||
from safety_schemas.models.events.types import ToolType
|
||||
|
||||
|
||||
from .render import (
|
||||
ask_codebase_setup,
|
||||
ask_continue,
|
||||
ask_firewall_setup,
|
||||
progressive_print,
|
||||
render_header,
|
||||
typed_print,
|
||||
)
|
||||
from safety.scan.init_scan import start_scan
|
||||
from ..cli_util import (
|
||||
CommandType,
|
||||
FeatureType,
|
||||
SafetyCLICommand,
|
||||
SafetyCLISubGroup,
|
||||
)
|
||||
from safety.error_handlers import handle_cmd_exception
|
||||
import typer
|
||||
|
||||
|
||||
from safety.init.constants import (
|
||||
MSG_ANALYZE_CODEBASE_TITLE,
|
||||
MSG_CODEBASE_FAILED_TO_SCAN,
|
||||
MSG_CODEBASE_NOT_CONFIGURED,
|
||||
MSG_CODEBASE_URL_DESCRIPTION,
|
||||
MSG_COMPLETE_SECURED,
|
||||
MSG_COMPLETE_TOOL_SECURED,
|
||||
MSG_FIREWALL_UNINSTALL,
|
||||
MSG_LAST_MANUAL_STEP,
|
||||
MSG_NO_VULNERABILITIES_FOUND,
|
||||
MSG_NO_VULNS_CODEBASE_URL_DESCRIPTION,
|
||||
MSG_OPEN_DASHBOARD_PROMPT,
|
||||
MSG_SETUP_CODEBASE_NO_PROJECT,
|
||||
MSG_SETUP_COMPLETE_SUBTITLE,
|
||||
MSG_SETUP_COMPLETE_TITLE,
|
||||
MSG_SETUP_INCOMPLETE,
|
||||
MSG_SETUP_NEXT_STEPS,
|
||||
MSG_SETUP_NEXT_STEPS_MANUAL_STEP,
|
||||
MSG_SETUP_NEXT_STEPS_NO_PROJECT,
|
||||
MSG_SETUP_NEXT_STEPS_NO_VULNS,
|
||||
MSG_SETUP_NEXT_STEPS_SUBTITLE,
|
||||
MSG_SETUP_PACKAGE_FIREWALL_DESCRIPTION,
|
||||
MSG_SETUP_PACKAGE_FIREWALL_TITLE,
|
||||
MSG_SETUP_CODEBASE_DESCRIPTION,
|
||||
MSG_SETUP_CODEBASE_TITLE,
|
||||
CODEBASE_INIT_CMD_NAME,
|
||||
CODEBASE_INIT_HELP,
|
||||
CODEBASE_INIT_DIRECTORY_HELP,
|
||||
MSG_TOOLS_NOT_CONFIGURED,
|
||||
MSG_WELCOME_TITLE,
|
||||
MSG_WELCOME_DESCRIPTION,
|
||||
)
|
||||
from safety.init.main import create_project, launch_auth_if_needed, setup_firewall
|
||||
from safety.console import (
|
||||
get_spinner_animation,
|
||||
main_console as console,
|
||||
should_use_ascii,
|
||||
)
|
||||
from ..tool.main import (
|
||||
configure_local_directory,
|
||||
find_local_tool_files,
|
||||
)
|
||||
|
||||
from ..constants import CONTEXT_COMMAND_TYPE, CONTEXT_FEATURE_TYPE
|
||||
|
||||
from safety.decorators import notify
|
||||
from safety.events.utils import emit_firewall_configured, emit_init_started
|
||||
from safety_schemas.models.events.payloads import AliasConfig, IndexConfig, InitExitStep
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import typer
|
||||
from safety.scan.init_scan import ScanResult
|
||||
|
||||
try:
|
||||
from typing import Annotated # type: ignore
|
||||
except ImportError:
|
||||
from typing_extensions import Annotated
|
||||
|
||||
init_app = typer.Typer(rich_markup_mode="rich", cls=SafetyCLISubGroup)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class InitScanState:
|
||||
"""
|
||||
Class to track scan state for vulnerability scans
|
||||
|
||||
Attributes:
|
||||
scan_id: ID of the scan
|
||||
dependencies: Number of dependencies found
|
||||
critical: Count of critical vulnerabilities
|
||||
high: Count of high severity vulnerabilities
|
||||
medium: Count of medium severity vulnerabilities
|
||||
low: Count of low severity vulnerabilities
|
||||
fixes: Number of fixes available
|
||||
fixed_vulns: Number of vulnerabilities with fixes
|
||||
url: URL to view the scan results
|
||||
completed: Whether the scan has completed
|
||||
progress: Percentage progress of the scan
|
||||
status_message: Current status message from the scanner
|
||||
status_action: Current action being performed by the scanner
|
||||
current_file: Current file being processed
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.scan_id: Optional[str] = None
|
||||
self.dependencies: int = 0
|
||||
self.critical: int = 0
|
||||
self.high: int = 0
|
||||
self.medium: int = 0
|
||||
self.low: int = 0
|
||||
self.others: int = 0
|
||||
self.vulns_count: int = 0
|
||||
self.fixes: int = 0
|
||||
self.fixed_vulns: int = 0
|
||||
self.codebase_url: Optional[str] = None
|
||||
self.completed: bool = False
|
||||
self.progress: int = 0
|
||||
self.status_message: Optional[str] = None
|
||||
self.status_action: Optional[str] = None
|
||||
self.current_file: Optional[str] = None
|
||||
|
||||
|
||||
def generate_summary(state: InitScanState, spinner_phase=0) -> Text:
|
||||
"""
|
||||
Generate the summary text based on current scan state
|
||||
"""
|
||||
spinner = get_spinner_animation()
|
||||
|
||||
text_markup = f"Tested {state.dependencies} dependenc{'y' if state.dependencies == 1 else 'ies'} for security issues\n"
|
||||
text_markup += "\nFound:\n\n"
|
||||
|
||||
categories = [
|
||||
{
|
||||
"name": "CRITICAL",
|
||||
"icon": ":fire:",
|
||||
"style": "bold red",
|
||||
"dim_style": "dim red",
|
||||
"count_attr": "critical",
|
||||
"spinner_offset": 0,
|
||||
},
|
||||
{
|
||||
"name": "HIGH",
|
||||
"icon": ":yellow_circle:",
|
||||
"style": "bold yellow",
|
||||
"dim_style": "dim yellow",
|
||||
"count_attr": "high",
|
||||
"spinner_offset": 2,
|
||||
},
|
||||
{
|
||||
"name": "MEDIUM",
|
||||
"icon": "!!",
|
||||
"style": "yellow",
|
||||
"dim_style": "dim yellow",
|
||||
"count_attr": "medium",
|
||||
"spinner_offset": 4,
|
||||
},
|
||||
{
|
||||
"name": "LOW",
|
||||
"icon": ":icon_info: ",
|
||||
"style": "bold blue",
|
||||
"dim_style": "dim blue",
|
||||
"count_attr": "low",
|
||||
"spinner_offset": 6,
|
||||
},
|
||||
{
|
||||
"name": "OTHER",
|
||||
"icon": "**",
|
||||
"style": "blue",
|
||||
"dim_style": "dim blue",
|
||||
"count_attr": "others",
|
||||
"spinner_offset": 8,
|
||||
},
|
||||
]
|
||||
|
||||
# No vulnerabilities case
|
||||
prepend_text_codebase_url = MSG_CODEBASE_URL_DESCRIPTION
|
||||
|
||||
if state.completed and state.vulns_count <= 0:
|
||||
text_markup += MSG_NO_VULNERABILITIES_FOUND
|
||||
prepend_text_codebase_url = MSG_NO_VULNS_CODEBASE_URL_DESCRIPTION
|
||||
else:
|
||||
for category in categories:
|
||||
category_count = getattr(state, category["count_attr"])
|
||||
|
||||
if state.completed and category_count == 0:
|
||||
continue
|
||||
|
||||
style = category["style"]
|
||||
text_markup += f"[{style}]{category['icon']} {category['name']}: [/{style}]"
|
||||
|
||||
if category_count > 0:
|
||||
vulns_word = f"vulnerabilit{'y' if category_count == 1 else 'ies'}"
|
||||
text_markup += f"[{style}]{category_count}[/{style}] {vulns_word}\n"
|
||||
else:
|
||||
spinner_status = spinner[
|
||||
(spinner_phase + category["spinner_offset"]) % len(spinner)
|
||||
]
|
||||
style = category["dim_style"]
|
||||
text_markup += f"[{style}]{spinner_status}[/{style}] Scanning\n"
|
||||
|
||||
# Show fixes info if we have vulnerabilities
|
||||
if state.vulns_count > 0 and state.fixes is not None:
|
||||
text_markup += "\n"
|
||||
|
||||
if state.fixes > 0:
|
||||
fix_word = f"fix{'' if state.fixes == 1 else 'es'}"
|
||||
vulns_word = f"vulnerabilit{'y' if state.fixed_vulns == 1 else 'ies'}"
|
||||
text_markup += f":sparkles: [green]{state.fixes}[/green] automatic {fix_word} available, resolving {state.fixed_vulns} {vulns_word}\n"
|
||||
else:
|
||||
text_markup += (
|
||||
" No automatic fixes available for these vulnerabilities\n"
|
||||
)
|
||||
|
||||
# Dashboard link if URL is available
|
||||
if state.codebase_url is not None:
|
||||
text_markup += f"\n{prepend_text_codebase_url}[blue underline]:link: {state.codebase_url}\n[/blue underline]"
|
||||
|
||||
elif state.completed:
|
||||
text_markup += "\n"
|
||||
|
||||
return console.render_str(text_markup)
|
||||
|
||||
|
||||
def generate_status_updates(state: InitScanState, spinner_phase: int = 0) -> Text:
|
||||
"""
|
||||
Generate text displaying current status updates and progress information
|
||||
|
||||
Args:
|
||||
state: The InitScanState object containing status information
|
||||
spinner_phase: Current phase of the spinner animation
|
||||
|
||||
Returns:
|
||||
Rich Text object containing formatted status updates
|
||||
"""
|
||||
spinner = get_spinner_animation()
|
||||
|
||||
markup_text = f"[cyan]{spinner[spinner_phase % len(spinner)]} Scanning...[/cyan]"
|
||||
|
||||
# Display current status message if available
|
||||
if state.status_message:
|
||||
action_symbol = {
|
||||
"init": ":arrows_counterclockwise:",
|
||||
"scanning": ":magnifying_glass_tilted_left:",
|
||||
"uploading": ":cloud: ",
|
||||
"complete": ":white_heavy_check_mark:",
|
||||
"error": ":cross_mark:",
|
||||
}
|
||||
status_key = state.status_action if state.status_action is not None else "init"
|
||||
action_symbol = action_symbol.get(status_key, ":information_source: ")
|
||||
|
||||
markup_text = f"\n[bold cyan]{action_symbol} Status: [/bold cyan][cyan]{state.status_message}[/cyan]\n"
|
||||
|
||||
# If we're processing a file, show that
|
||||
if state.current_file and state.status_action == "scanning":
|
||||
markup_text += f"[bold cyan]:file_folder: Current file: [/bold cyan][dim cyan]{state.current_file}[/dim cyan]\n"
|
||||
|
||||
# Ensure progress is capped at 100%
|
||||
display_progress = min(state.progress, 100)
|
||||
markup_text += f"[cyan]:bar_chart: Progress: {display_progress}%[/cyan]\n"
|
||||
|
||||
return console.render_str(markup_text)
|
||||
|
||||
|
||||
def process_scan_results(
|
||||
scan_results: Iterator["ScanResult"], state: InitScanState
|
||||
) -> None:
|
||||
"""Process the scan iterator and update state from typed result models
|
||||
|
||||
Args:
|
||||
scan_results: Iterator yielding scan results from init_scan
|
||||
state: The InitScanState object to update with scan results
|
||||
"""
|
||||
# Import the scan result types to handle typed results
|
||||
from safety.scan.init_scan import (
|
||||
ScanResultType,
|
||||
)
|
||||
|
||||
try:
|
||||
for result in scan_results:
|
||||
# Now result is a typed model with proper attributes
|
||||
if result.type == ScanResultType.INIT:
|
||||
# Initial state with dependency count
|
||||
state.dependencies = result.dependencies
|
||||
state.status_message = "Initializing scan"
|
||||
state.status_action = "init"
|
||||
|
||||
elif result.type == ScanResultType.PROGRESS:
|
||||
# Update all the state fields from the progress result
|
||||
# Type checker knows result is ProgressScanResult
|
||||
# Ensure progress never exceeds 100%
|
||||
state.progress = min(result.percent, 100)
|
||||
state.dependencies = result.dependencies
|
||||
|
||||
# Track current file being processed
|
||||
state.current_file = result.file
|
||||
state.status_message = f"Processing {result.file}"
|
||||
state.status_action = "scanning"
|
||||
|
||||
# Update severity counts if present
|
||||
if result.critical is not None:
|
||||
state.critical = result.critical
|
||||
if result.high is not None:
|
||||
state.high = result.high
|
||||
if result.medium is not None:
|
||||
state.medium = result.medium
|
||||
if result.low is not None:
|
||||
state.low = result.low
|
||||
if result.others is not None:
|
||||
state.others = result.others
|
||||
|
||||
# Update vulnerability count
|
||||
if result.vulns_count is not None:
|
||||
state.vulns_count = result.vulns_count
|
||||
|
||||
# Update fix information if present
|
||||
if result.fixes is not None:
|
||||
state.fixes = result.fixes
|
||||
if result.fixed_vulns is not None:
|
||||
state.fixed_vulns = result.fixed_vulns
|
||||
|
||||
elif result.type == ScanResultType.STATUS:
|
||||
# Generic status update
|
||||
state.status_message = result.message
|
||||
state.status_action = result.action
|
||||
if result.percent is not None:
|
||||
state.progress = min(result.percent, 100)
|
||||
|
||||
elif result.type == ScanResultType.UPLOADING:
|
||||
# Status update for uploading phase
|
||||
state.status_message = result.message
|
||||
state.status_action = "uploading"
|
||||
if result.percent is not None:
|
||||
state.progress = min(result.percent, 100)
|
||||
|
||||
elif result.type == ScanResultType.COMPLETE:
|
||||
# Final update with complete data
|
||||
# Type checker knows result is CompleteScanResult
|
||||
state.progress = 100
|
||||
state.dependencies = result.dependencies
|
||||
state.critical = result.critical
|
||||
state.high = result.high
|
||||
state.medium = result.medium
|
||||
state.low = result.low
|
||||
state.others = result.others
|
||||
state.fixes = result.fixes
|
||||
state.fixed_vulns = result.fixed_vulns
|
||||
state.status_message = "Scan completed"
|
||||
state.status_action = "complete"
|
||||
state.vulns_count = result.vulns_count
|
||||
|
||||
# Update project URL if available
|
||||
if result.codebase_url:
|
||||
state.codebase_url = result.codebase_url
|
||||
|
||||
if result.scan_id:
|
||||
state.scan_id = result.scan_id
|
||||
|
||||
# We're done processing
|
||||
state.completed = True
|
||||
|
||||
# Add a small delay between updates to allow UI thread to refresh
|
||||
time.sleep(0.05)
|
||||
except Exception as e:
|
||||
console.print(f"Error processing scan results: {e}", style="bold red")
|
||||
state.status_message = f"Error: {str(e)}"
|
||||
state.status_action = "error"
|
||||
finally:
|
||||
# Ensure we mark as completed even if there was an exception
|
||||
state.completed = True
|
||||
|
||||
|
||||
def init_scan_ui(ctx: "typer.Context", prompt_user: bool = False) -> InitScanState:
|
||||
"""
|
||||
Initialize and run a scan for the init command, showing a live UI with scan progress.
|
||||
Uses the start_scan function to get an iterator of scan results and displays UI based on them.
|
||||
|
||||
Args:
|
||||
ctx: The Typer context object containing configuration and project information
|
||||
"""
|
||||
# Initialize state for tracking scan progress
|
||||
state = InitScanState()
|
||||
|
||||
# Set up scan parameters and get the scan iterator
|
||||
target = ctx.obj.project.project_path.parent
|
||||
use_server_matching = False
|
||||
|
||||
# Start the scan using the dedicated function
|
||||
scan_results = start_scan(
|
||||
ctx=ctx,
|
||||
target=target,
|
||||
use_server_matching=use_server_matching,
|
||||
auth_type=ctx.obj.auth.client.get_authentication_type(),
|
||||
is_authenticated=ctx.obj.auth.client.is_using_auth_credentials(),
|
||||
client=ctx.obj.auth.client,
|
||||
project=ctx.obj.project,
|
||||
platform_enabled=ctx.obj.platform_enabled,
|
||||
)
|
||||
|
||||
# Process the scan results in a separate thread
|
||||
scan_thread = threading.Thread(
|
||||
target=process_scan_results, args=(scan_results, state)
|
||||
)
|
||||
scan_thread.daemon = True
|
||||
scan_thread.start()
|
||||
|
||||
# Handle UI updates in the main thread
|
||||
spinner_phase = 0
|
||||
render_header(
|
||||
MSG_ANALYZE_CODEBASE_TITLE.format(project_name=ctx.obj.project.id),
|
||||
emoji=":magnifying_glass_tilted_left:",
|
||||
)
|
||||
time.sleep(0.8)
|
||||
|
||||
# Detect if running on Windows
|
||||
is_windows = sys.platform == "win32" or should_use_ascii()
|
||||
|
||||
# Alternate screen in Windows is buggy, so we disable it
|
||||
live_kwargs = {
|
||||
"refresh_per_second": 10,
|
||||
"screen": False if is_windows else True,
|
||||
"transient": False,
|
||||
}
|
||||
|
||||
refresh_sleep = 0.1
|
||||
|
||||
with Live(**live_kwargs) as live:
|
||||
while not state.completed or scan_thread.is_alive():
|
||||
# Update spinner phase for animation
|
||||
spinner_phase = (spinner_phase + 1) % 10
|
||||
|
||||
# Summary information shown below status updates
|
||||
summary = generate_summary(state, spinner_phase)
|
||||
|
||||
if is_windows:
|
||||
content = summary
|
||||
else:
|
||||
# Create a container for all UI elements
|
||||
container = Table.grid(padding=0, expand=True)
|
||||
container.add_row(None)
|
||||
container.add_row(
|
||||
Panel(
|
||||
generate_status_updates(state, spinner_phase),
|
||||
border_style="cyan",
|
||||
padding=(0, 1),
|
||||
)
|
||||
)
|
||||
container.add_row(None)
|
||||
container.add_row(summary)
|
||||
content = container
|
||||
|
||||
# Display the updated UI
|
||||
live.update(content)
|
||||
|
||||
time.sleep(refresh_sleep)
|
||||
|
||||
# Last sync
|
||||
if state.completed:
|
||||
live.update(generate_summary(state, spinner_phase))
|
||||
time.sleep(2)
|
||||
|
||||
# Windows is not using alternate screen, so summary is already rendered
|
||||
if not is_windows:
|
||||
# Final update to ensure completion state is shown
|
||||
console.print(generate_summary(state))
|
||||
|
||||
if state.codebase_url:
|
||||
typed_print(MSG_OPEN_DASHBOARD_PROMPT, end_line=False)
|
||||
should_open = "y"
|
||||
|
||||
if prompt_user:
|
||||
should_open = Prompt.ask(
|
||||
"",
|
||||
choices=["y", "n", "Y", "N"],
|
||||
default="y",
|
||||
show_default=False,
|
||||
show_choices=False,
|
||||
console=console,
|
||||
).lower()
|
||||
|
||||
if should_open == "y":
|
||||
typer.launch(state.codebase_url)
|
||||
|
||||
return state
|
||||
|
||||
|
||||
@init_app.command(
|
||||
cls=SafetyCLICommand,
|
||||
help=CODEBASE_INIT_HELP,
|
||||
name=CODEBASE_INIT_CMD_NAME,
|
||||
options_metavar="[OPTIONS]",
|
||||
context_settings={
|
||||
"allow_extra_args": True,
|
||||
"ignore_unknown_options": True,
|
||||
CONTEXT_COMMAND_TYPE: CommandType.BETA,
|
||||
CONTEXT_FEATURE_TYPE: FeatureType.FIREWALL,
|
||||
},
|
||||
)
|
||||
@handle_cmd_exception
|
||||
@notify
|
||||
def init(
|
||||
ctx: typer.Context,
|
||||
directory: Annotated[
|
||||
Path,
|
||||
typer.Argument( # type: ignore
|
||||
exists=True,
|
||||
file_okay=False,
|
||||
dir_okay=True,
|
||||
writable=False,
|
||||
readable=True,
|
||||
resolve_path=True,
|
||||
show_default=False,
|
||||
help=CODEBASE_INIT_DIRECTORY_HELP,
|
||||
),
|
||||
] = Path("."),
|
||||
):
|
||||
emit_init_started(ctx.obj.event_bus, ctx)
|
||||
# TODO: check if tty is available
|
||||
tracker = StepTracker()
|
||||
try:
|
||||
do_init(ctx, directory, tracker, prompt_user=console.is_interactive)
|
||||
except KeyboardInterrupt as e:
|
||||
emit_init_exited(ctx.obj.event_bus, ctx, exit_step=tracker.current_step)
|
||||
raise e
|
||||
|
||||
|
||||
def do_init(
|
||||
ctx: typer.Context, directory: Path, tracker: StepTracker, prompt_user: bool = True
|
||||
):
|
||||
"""
|
||||
Initialize Safety CLI with the new onboarding flow.
|
||||
|
||||
Args:
|
||||
ctx: The CLI context
|
||||
directory: The target directory to initialize
|
||||
prompt_user: Whether to prompt the user for input or use defaults
|
||||
"""
|
||||
project_dir = directory.resolve()
|
||||
|
||||
typed_print(MSG_WELCOME_TITLE)
|
||||
progressive_print(MSG_WELCOME_DESCRIPTION)
|
||||
|
||||
tracker.current_step = InitExitStep.PRE_AUTH
|
||||
org_slug = launch_auth_if_needed(ctx, console)
|
||||
tracker.current_step = InitExitStep.POST_AUTH
|
||||
|
||||
render_header(MSG_SETUP_PACKAGE_FIREWALL_TITLE, margin_right=1)
|
||||
console.print(MSG_SETUP_PACKAGE_FIREWALL_DESCRIPTION)
|
||||
|
||||
console.print(
|
||||
Syntax(
|
||||
MSG_FIREWALL_UNINSTALL, "bash", theme="monokai", background_color="default"
|
||||
)
|
||||
)
|
||||
|
||||
completed_tools = ""
|
||||
all_completed = False
|
||||
all_missing = True
|
||||
|
||||
status = {
|
||||
ToolType.PIP: {
|
||||
"alias": AliasConfig(is_configured=False),
|
||||
"index": IndexConfig(is_configured=False),
|
||||
},
|
||||
ToolType.POETRY: {
|
||||
"alias": AliasConfig(is_configured=False),
|
||||
"index": IndexConfig(is_configured=False),
|
||||
},
|
||||
ToolType.UV: {
|
||||
"alias": AliasConfig(is_configured=False),
|
||||
"index": IndexConfig(is_configured=False),
|
||||
},
|
||||
ToolType.NPM: {
|
||||
"alias": AliasConfig(is_configured=False),
|
||||
"index": IndexConfig(is_configured=False),
|
||||
},
|
||||
}
|
||||
|
||||
tracker.current_step = InitExitStep.PRE_FIREWALL_SETUP
|
||||
if ask_firewall_setup(ctx, prompt_user):
|
||||
completed_tools, all_completed, all_missing, status = setup_firewall(
|
||||
ctx, status, org_slug, console
|
||||
)
|
||||
console.line()
|
||||
ask_continue(ctx, prompt_user)
|
||||
console.line()
|
||||
|
||||
tracker.current_step = InitExitStep.POST_FIREWALL_SETUP
|
||||
|
||||
render_header(MSG_SETUP_CODEBASE_TITLE, emoji=":locked:")
|
||||
console.print(MSG_SETUP_CODEBASE_DESCRIPTION)
|
||||
|
||||
project_scan_state = None
|
||||
|
||||
tracker.current_step = InitExitStep.PRE_CODEBASE_SETUP
|
||||
local_files = find_local_tool_files(project_dir)
|
||||
|
||||
emit_codebase_detection_status(
|
||||
event_bus=ctx.obj.event_bus,
|
||||
ctx=ctx,
|
||||
detected=any(local_files),
|
||||
detected_files=local_files if local_files else None,
|
||||
)
|
||||
|
||||
if local_files:
|
||||
progressive_print(
|
||||
[
|
||||
f":pushpin: We found a `{file.name}` file in this directory."
|
||||
for file in local_files
|
||||
]
|
||||
)
|
||||
|
||||
console.line()
|
||||
|
||||
if ask_codebase_setup(ctx, prompt_user):
|
||||
unverified_project = load_unverified_project_from_config(
|
||||
project_root=project_dir
|
||||
)
|
||||
link_behavior = "prompt"
|
||||
|
||||
if unverified_project.created:
|
||||
link_behavior = "always"
|
||||
|
||||
project_created, project_status = create_project(
|
||||
ctx,
|
||||
console,
|
||||
project_dir,
|
||||
unverified_project=unverified_project,
|
||||
link_behavior=link_behavior,
|
||||
)
|
||||
|
||||
configure_local_directory(project_dir, org_slug, ctx.obj.project.id)
|
||||
|
||||
emit_codebase_setup_completed(
|
||||
event_bus=ctx.obj.event_bus,
|
||||
ctx=ctx,
|
||||
is_created=project_created,
|
||||
codebase_id=ctx.obj.project.id if project_created else None,
|
||||
)
|
||||
|
||||
if project_created:
|
||||
console.print(
|
||||
"\n"
|
||||
+ f"{ctx.obj.project.id} codebase {project_status} :white_heavy_check_mark:"
|
||||
)
|
||||
else:
|
||||
progressive_print([":x: Failed to create codebase"])
|
||||
|
||||
console.line()
|
||||
|
||||
tracker.current_step = InitExitStep.PRE_SCAN
|
||||
project_scan_state = init_scan_ui(ctx, prompt_user)
|
||||
tracker.current_step = InitExitStep.POST_SCAN
|
||||
emit_init_scan_completed(
|
||||
event_bus=ctx.obj.event_bus,
|
||||
ctx=ctx,
|
||||
scan_id=project_scan_state.scan_id,
|
||||
)
|
||||
else:
|
||||
console.print(MSG_SETUP_CODEBASE_NO_PROJECT)
|
||||
|
||||
tracker.current_step = InitExitStep.POST_CODEBASE_SETUP
|
||||
|
||||
console.line()
|
||||
render_header(MSG_SETUP_COMPLETE_TITLE, emoji=":trophy:")
|
||||
|
||||
is_setup_complete = all_completed and project_scan_state
|
||||
|
||||
wrap_up_msg = []
|
||||
|
||||
if all_completed:
|
||||
wrap_up_msg.append(
|
||||
MSG_COMPLETE_TOOL_SECURED.format(
|
||||
tools=completed_tools,
|
||||
firewall_url="https://platform.safetycli.com/firewall/",
|
||||
)
|
||||
)
|
||||
elif all_missing:
|
||||
wrap_up_msg.append(MSG_TOOLS_NOT_CONFIGURED)
|
||||
else:
|
||||
wrap_up_msg.append(MSG_SETUP_INCOMPLETE)
|
||||
|
||||
wrap_up_msg.append("")
|
||||
|
||||
if project_scan_state:
|
||||
if project_scan_state.scan_id:
|
||||
wrap_up_msg.append(
|
||||
MSG_COMPLETE_SECURED.format(
|
||||
codebase_url=project_scan_state.codebase_url
|
||||
)
|
||||
)
|
||||
else:
|
||||
msg = (
|
||||
project_scan_state.status_message
|
||||
if project_scan_state.status_message
|
||||
else "Unknown"
|
||||
)
|
||||
wrap_up_msg.append(MSG_CODEBASE_FAILED_TO_SCAN.format(reason=msg))
|
||||
else:
|
||||
wrap_up_msg.append(MSG_CODEBASE_NOT_CONFIGURED)
|
||||
|
||||
if wrap_up_msg:
|
||||
progressive_print(wrap_up_msg)
|
||||
console.line()
|
||||
|
||||
if is_setup_complete:
|
||||
typed_print(MSG_SETUP_COMPLETE_SUBTITLE)
|
||||
console.line()
|
||||
typed_print(MSG_LAST_MANUAL_STEP)
|
||||
console.line()
|
||||
|
||||
render_header(title=MSG_SETUP_NEXT_STEPS_SUBTITLE, emoji=":rocket:")
|
||||
console.line()
|
||||
|
||||
next_steps_msg = MSG_SETUP_NEXT_STEPS
|
||||
|
||||
if not project_scan_state:
|
||||
next_steps_msg = MSG_SETUP_NEXT_STEPS_NO_PROJECT
|
||||
elif project_scan_state.vulns_count <= 0:
|
||||
next_steps_msg = MSG_SETUP_NEXT_STEPS_NO_VULNS
|
||||
|
||||
progressive_print(
|
||||
[Padding(console.render_str(line), (0, 0, 1, 0)) for line in next_steps_msg]
|
||||
)
|
||||
|
||||
console.line()
|
||||
typed_print(MSG_SETUP_NEXT_STEPS_MANUAL_STEP, delay=0.04)
|
||||
console.line()
|
||||
|
||||
# Emit event for firewall configuration
|
||||
emit_firewall_configured(
|
||||
event_bus=ctx.obj.event_bus,
|
||||
status=status,
|
||||
)
|
||||
tracker.current_step = InitExitStep.COMPLETED
|
||||
Reference in New Issue
Block a user