updates
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,177 @@
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from safety.codebase.render import render_initialization_result
|
||||
from safety.errors import SafetyError
|
||||
from safety.events.utils.emission import (
|
||||
emit_codebase_detection_status,
|
||||
emit_codebase_setup_completed,
|
||||
)
|
||||
from safety.init.main import launch_auth_if_needed
|
||||
from safety.tool.main import find_local_tool_files
|
||||
from safety.util import clean_project_id
|
||||
from typing_extensions import Annotated
|
||||
|
||||
import typer
|
||||
from safety.cli_util import SafetyCLISubGroup, SafetyCLICommand
|
||||
from .constants import (
|
||||
CMD_CODEBASE_INIT_NAME,
|
||||
CMD_HELP_CODEBASE_INIT,
|
||||
CMD_HELP_CODEBASE,
|
||||
CMD_CODEBASE_GROUP_NAME,
|
||||
CMD_HELP_CODEBASE_INIT_DISABLE_FIREWALL,
|
||||
CMD_HELP_CODEBASE_INIT_LINK_TO,
|
||||
CMD_HELP_CODEBASE_INIT_NAME,
|
||||
CMD_HELP_CODEBASE_INIT_PATH,
|
||||
)
|
||||
|
||||
from ..cli_util import CommandType, get_command_for
|
||||
from ..error_handlers import handle_cmd_exception
|
||||
from ..decorators import notify
|
||||
from ..constants import CONTEXT_COMMAND_TYPE, DEFAULT_EPILOG
|
||||
from safety.console import main_console as console
|
||||
from .main import initialize_codebase, prepare_unverified_codebase
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
cli_apps_opts = {
|
||||
"rich_markup_mode": "rich",
|
||||
"cls": SafetyCLISubGroup,
|
||||
"name": CMD_CODEBASE_GROUP_NAME,
|
||||
}
|
||||
codebase_app = typer.Typer(**cli_apps_opts)
|
||||
|
||||
DEFAULT_CMD = CMD_CODEBASE_INIT_NAME
|
||||
|
||||
|
||||
@codebase_app.callback(
|
||||
invoke_without_command=True,
|
||||
cls=SafetyCLISubGroup,
|
||||
help=CMD_HELP_CODEBASE,
|
||||
epilog=DEFAULT_EPILOG,
|
||||
context_settings={
|
||||
"allow_extra_args": True,
|
||||
"ignore_unknown_options": True,
|
||||
CONTEXT_COMMAND_TYPE: CommandType.BETA,
|
||||
},
|
||||
)
|
||||
def codebase(
|
||||
ctx: typer.Context,
|
||||
):
|
||||
"""
|
||||
Group command for Safety Codebase (project) operations. Running this command will forward to the default command.
|
||||
"""
|
||||
logger.info("codebase started")
|
||||
|
||||
# If no subcommand is invoked, forward to the default command
|
||||
if not ctx.invoked_subcommand:
|
||||
default_command = get_command_for(name=DEFAULT_CMD, typer_instance=codebase_app)
|
||||
return ctx.forward(default_command)
|
||||
|
||||
|
||||
@codebase_app.command(
|
||||
cls=SafetyCLICommand,
|
||||
help=CMD_HELP_CODEBASE_INIT,
|
||||
name=CMD_CODEBASE_INIT_NAME,
|
||||
epilog=DEFAULT_EPILOG,
|
||||
options_metavar="[OPTIONS]",
|
||||
context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
|
||||
)
|
||||
@handle_cmd_exception
|
||||
@notify
|
||||
def init(
|
||||
ctx: typer.Context,
|
||||
name: Annotated[
|
||||
Optional[str],
|
||||
typer.Option(
|
||||
help=CMD_HELP_CODEBASE_INIT_NAME,
|
||||
callback=lambda name: clean_project_id(name) if name else None,
|
||||
),
|
||||
] = None,
|
||||
link_to: Annotated[
|
||||
Optional[str],
|
||||
typer.Option(
|
||||
"--link-to",
|
||||
help=CMD_HELP_CODEBASE_INIT_LINK_TO,
|
||||
callback=lambda name: clean_project_id(name) if name else None,
|
||||
),
|
||||
] = None,
|
||||
skip_firewall_setup: Annotated[
|
||||
bool, typer.Option(help=CMD_HELP_CODEBASE_INIT_DISABLE_FIREWALL)
|
||||
] = False,
|
||||
codebase_path: Annotated[
|
||||
Path,
|
||||
typer.Option(
|
||||
"--path",
|
||||
exists=True,
|
||||
file_okay=False,
|
||||
dir_okay=True,
|
||||
writable=True,
|
||||
readable=True,
|
||||
resolve_path=True,
|
||||
show_default=False,
|
||||
help=CMD_HELP_CODEBASE_INIT_PATH,
|
||||
),
|
||||
] = Path("."),
|
||||
):
|
||||
"""
|
||||
Initialize a Safety Codebase. The codebase may be entirely new to Safety Platform,
|
||||
or may already exist in Safety Platform and the user is wanting to initialize it locally.
|
||||
"""
|
||||
logger.info("codebase init started")
|
||||
|
||||
if link_to and name:
|
||||
raise typer.BadParameter("--link-to and --name cannot be used together")
|
||||
|
||||
org_slug = launch_auth_if_needed(ctx, console)
|
||||
|
||||
if not org_slug:
|
||||
raise SafetyError(
|
||||
"Organization not found, please run 'safety auth status' or 'safety auth login'"
|
||||
)
|
||||
|
||||
should_enable_firewall = not skip_firewall_setup and ctx.obj.firewall_enabled
|
||||
|
||||
unverified_codebase = prepare_unverified_codebase(
|
||||
codebase_path=codebase_path,
|
||||
user_provided_name=name,
|
||||
user_provided_link_to=link_to,
|
||||
)
|
||||
|
||||
local_files = find_local_tool_files(codebase_path)
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
project_file_created, project_status = initialize_codebase(
|
||||
ctx=ctx,
|
||||
console=console,
|
||||
codebase_path=codebase_path,
|
||||
unverified_codebase=unverified_codebase,
|
||||
org_slug=org_slug,
|
||||
link_to=link_to,
|
||||
should_enable_firewall=should_enable_firewall,
|
||||
)
|
||||
|
||||
codebase_init_status = (
|
||||
"reinitialized" if unverified_codebase.created else project_status
|
||||
)
|
||||
codebase_id = ctx.obj.project.id if ctx.obj.project and ctx.obj.project.id else None
|
||||
|
||||
render_initialization_result(
|
||||
console=console,
|
||||
codebase_init_status=codebase_init_status,
|
||||
codebase_id=codebase_id,
|
||||
)
|
||||
|
||||
emit_codebase_setup_completed(
|
||||
event_bus=ctx.obj.event_bus,
|
||||
ctx=ctx,
|
||||
is_created=project_file_created,
|
||||
codebase_id=codebase_id,
|
||||
)
|
||||
@@ -0,0 +1,27 @@
|
||||
CMD_HELP_CODEBASE_INIT = "Initialize a Safety Codebase (like git init for security). Sets up a new codebase or connects your local project to an existing one on Safety Platform."
|
||||
CMD_HELP_CODEBASE = (
|
||||
"[BETA] Manage your Safety Codebase integration.\nExample: safety codebase init"
|
||||
)
|
||||
|
||||
CMD_CODEBASE_GROUP_NAME = "codebase"
|
||||
CMD_CODEBASE_INIT_NAME = "init"
|
||||
|
||||
|
||||
# init options help
|
||||
CMD_HELP_CODEBASE_INIT_NAME = "Name of the codebase. Defaults to GIT origin name, parent directory name, or random string if parent directory is unnamed. The value will be normalized for use as an identifier."
|
||||
CMD_HELP_CODEBASE_INIT_LINK_TO = (
|
||||
"Link to an existing codebase using its codebase slug (found in Safety Platform)."
|
||||
)
|
||||
CMD_HELP_CODEBASE_INIT_DISABLE_FIREWALL = "Don't enable Firewall protection for this codebase (enabled by default when available in your organization)"
|
||||
CMD_HELP_CODEBASE_INIT_PATH = (
|
||||
"Path to the codebase directory. Defaults to current directory."
|
||||
)
|
||||
|
||||
|
||||
CODEBASE_INIT_REINITIALIZED = "Reinitialized existing codebase {codebase_name}"
|
||||
CODEBASE_INIT_ALREADY_EXISTS = "A codebase already exists in this directory. Please delete .safety-project.ini and run `safety codebase init` again to initialize a new codebase."
|
||||
CODEBASE_INIT_NOT_FOUND_LINK_TO = "\nError: codebase '{codebase_name}' specified with --link-to does not exist.\n\nTo create a new codebase instead, use one of:\n safety codebase init\n safety codebase init --name \"custom name\"\n\nTo link to an existing codebase, verify the codebase id and try again."
|
||||
CODEBASE_INIT_NOT_FOUND_PROJECT_FILE = "\nError: codebase '{codebase_name}' specified with the current .safety-project.ini file does not exist.\n\nTo create a new codebase instead, delete the corrupted .safety-project.ini file and then use one of:\n safety codebase init\n safety codebase init --name \"custom name\"\n\nTo link to an existing codebase, verify the codebase id and try again."
|
||||
CODEBASE_INIT_LINKED = "Linked to codebase {codebase_name}."
|
||||
CODEBASE_INIT_CREATED = "Created new codebase {codebase_name}."
|
||||
CODEBASE_INIT_ERROR = "Error: unable to initialize the codebase. Please try again."
|
||||
@@ -0,0 +1,115 @@
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
from ..codebase_utils import load_unverified_project_from_config
|
||||
from safety.errors import SafetyError, SafetyException
|
||||
from pathlib import Path
|
||||
from safety.codebase.constants import (
|
||||
CODEBASE_INIT_ERROR,
|
||||
CODEBASE_INIT_NOT_FOUND_LINK_TO,
|
||||
CODEBASE_INIT_NOT_FOUND_PROJECT_FILE,
|
||||
)
|
||||
from safety.init.main import create_project
|
||||
from typer import Context
|
||||
from rich.console import Console
|
||||
import sys
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..codebase_utils import UnverifiedProjectModel
|
||||
|
||||
|
||||
def initialize_codebase(
|
||||
ctx: Context,
|
||||
console: Console,
|
||||
codebase_path: Path,
|
||||
unverified_codebase: "UnverifiedProjectModel",
|
||||
org_slug: str,
|
||||
link_to: Optional[str] = None,
|
||||
should_enable_firewall: bool = False,
|
||||
):
|
||||
is_interactive = sys.stdin.isatty()
|
||||
link_behavior = "prompt"
|
||||
create_if_missing = True
|
||||
is_codebase_file_created = unverified_codebase.created
|
||||
|
||||
if link_to or is_codebase_file_created:
|
||||
link_behavior = "always"
|
||||
create_if_missing = False
|
||||
elif not is_interactive:
|
||||
link_behavior = "never"
|
||||
|
||||
project_file_created, project_status = create_project(
|
||||
ctx=ctx,
|
||||
console=console,
|
||||
target=codebase_path,
|
||||
unverified_project=unverified_codebase,
|
||||
create_if_missing=create_if_missing,
|
||||
link_behavior=link_behavior,
|
||||
)
|
||||
|
||||
if project_status == "not_found":
|
||||
codebase_name = "Unknown"
|
||||
msg = "Codebase not found."
|
||||
|
||||
if link_to:
|
||||
msg = CODEBASE_INIT_NOT_FOUND_LINK_TO
|
||||
codebase_name = link_to
|
||||
elif is_codebase_file_created:
|
||||
msg = CODEBASE_INIT_NOT_FOUND_PROJECT_FILE
|
||||
codebase_name = unverified_codebase.id
|
||||
|
||||
raise SafetyError(msg.format(codebase_name=codebase_name))
|
||||
elif project_status == "found" and not is_interactive:
|
||||
# Non-TTY mode: Project exists but we can't link (link_behavior="never")
|
||||
suggested_name = unverified_codebase.id
|
||||
raise SafetyError(
|
||||
f"Project '{suggested_name}' already exists. "
|
||||
f"In non-interactive mode, use --link-to '{suggested_name}' to link to the existing project, "
|
||||
f"or use --name with a different project name to create a new one."
|
||||
)
|
||||
|
||||
if not ctx.obj.project:
|
||||
raise SafetyException(CODEBASE_INIT_ERROR)
|
||||
|
||||
if should_enable_firewall:
|
||||
from ..tool.main import configure_local_directory
|
||||
|
||||
configure_local_directory(codebase_path, org_slug, ctx.obj.project.id)
|
||||
|
||||
return project_file_created, project_status
|
||||
|
||||
|
||||
def fail_if_codebase_name_mismatch(
|
||||
provided_name: str,
|
||||
unverified_codebase: "UnverifiedProjectModel",
|
||||
) -> None:
|
||||
"""
|
||||
Useful to prevent the user from overwriting an existing codebase by mistyping the name.
|
||||
"""
|
||||
if unverified_codebase.id and provided_name != unverified_codebase.id:
|
||||
from safety.codebase.constants import CODEBASE_INIT_ALREADY_EXISTS
|
||||
|
||||
raise SafetyError(CODEBASE_INIT_ALREADY_EXISTS)
|
||||
|
||||
|
||||
def prepare_unverified_codebase(
|
||||
codebase_path: Path,
|
||||
user_provided_name: Optional[str] = None,
|
||||
user_provided_link_to: Optional[str] = None,
|
||||
) -> "UnverifiedProjectModel":
|
||||
"""
|
||||
Prepare the unverified codebase object based on the provided name and link to.
|
||||
"""
|
||||
unverified_codebase = load_unverified_project_from_config(
|
||||
project_root=codebase_path
|
||||
)
|
||||
|
||||
provided_name = user_provided_name or user_provided_link_to
|
||||
|
||||
if provided_name:
|
||||
fail_if_codebase_name_mismatch(
|
||||
provided_name=provided_name,
|
||||
unverified_codebase=unverified_codebase,
|
||||
)
|
||||
|
||||
unverified_codebase.id = provided_name
|
||||
|
||||
return unverified_codebase
|
||||
@@ -0,0 +1,34 @@
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from rich.console import Console
|
||||
|
||||
|
||||
def render_initialization_result(
|
||||
console: "Console",
|
||||
codebase_init_status: Optional[str] = None,
|
||||
codebase_id: Optional[str] = None,
|
||||
):
|
||||
if not codebase_init_status or not codebase_id:
|
||||
console.print("Error: unable to initialize codebase")
|
||||
return
|
||||
|
||||
message = None
|
||||
|
||||
if codebase_init_status == "created":
|
||||
from safety.codebase.constants import CODEBASE_INIT_CREATED
|
||||
|
||||
message = CODEBASE_INIT_CREATED
|
||||
|
||||
if codebase_init_status == "linked":
|
||||
from safety.codebase.constants import CODEBASE_INIT_LINKED
|
||||
|
||||
message = CODEBASE_INIT_LINKED
|
||||
|
||||
if codebase_init_status == "reinitialized":
|
||||
from safety.codebase.constants import CODEBASE_INIT_REINITIALIZED
|
||||
|
||||
message = CODEBASE_INIT_REINITIALIZED
|
||||
|
||||
if message:
|
||||
console.print(message.format(codebase_name=codebase_id))
|
||||
Reference in New Issue
Block a user