151 lines
4.4 KiB
Python
151 lines
4.4 KiB
Python
import logging
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
import typer
|
|
from rich.console import Console
|
|
|
|
from safety.tool.constants import (
|
|
PYPI_PUBLIC_REPOSITORY_URL,
|
|
PYPI_ORGANIZATION_REPOSITORY_URL,
|
|
PYPI_PROJECT_REPOSITORY_URL,
|
|
)
|
|
from safety.tool.resolver import get_unwrapped_command
|
|
from safety.utils.pyapp_utils import get_path, get_env
|
|
|
|
from safety.console import main_console
|
|
from safety.tool.auth import build_index_url
|
|
from ...encoding import detect_encoding
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class Pip:
|
|
@classmethod
|
|
def is_installed(cls) -> bool:
|
|
"""
|
|
Checks if the PIP program is installed
|
|
|
|
Returns:
|
|
True if PIP is installed on system, or false otherwise
|
|
"""
|
|
return shutil.which("pip", path=get_path()) is not None
|
|
|
|
@classmethod
|
|
def configure_requirements(
|
|
cls,
|
|
file: Path,
|
|
org_slug: Optional[str],
|
|
project_id: Optional[str],
|
|
console: Console = main_console,
|
|
) -> Optional[Path]:
|
|
"""
|
|
Configures Safety index url for specified requirements file.
|
|
|
|
Args:
|
|
file (Path): Path to requirements.txt file.
|
|
org_slug (str): Organization slug.
|
|
project_id (str): Project identifier.
|
|
console (Console): Console instance.
|
|
"""
|
|
|
|
with open(file, "r+", encoding=detect_encoding(file)) as f:
|
|
content = f.read()
|
|
|
|
repository_url = (
|
|
PYPI_PROJECT_REPOSITORY_URL.format(org_slug, project_id)
|
|
if project_id and org_slug
|
|
else (
|
|
PYPI_ORGANIZATION_REPOSITORY_URL.format(org_slug)
|
|
if org_slug
|
|
else PYPI_PUBLIC_REPOSITORY_URL
|
|
)
|
|
)
|
|
index_config = f"-i {repository_url}\n"
|
|
if content.find(index_config) == -1:
|
|
f.seek(0)
|
|
f.write(index_config + content)
|
|
|
|
logger.info(f"Configured {file} file")
|
|
return file
|
|
else:
|
|
logger.info(f"{file} is already configured. Skipping.")
|
|
|
|
return None
|
|
|
|
@classmethod
|
|
def configure_system(
|
|
cls, org_slug: Optional[str], console: Console = main_console
|
|
) -> Optional[Path]:
|
|
"""
|
|
Configures PIP system to use to Safety index url.
|
|
"""
|
|
try:
|
|
repository_url = (
|
|
PYPI_ORGANIZATION_REPOSITORY_URL.format(org_slug)
|
|
if org_slug
|
|
else PYPI_PUBLIC_REPOSITORY_URL
|
|
)
|
|
|
|
result = subprocess.run(
|
|
[
|
|
get_unwrapped_command(name="pip"),
|
|
"config",
|
|
"--user",
|
|
"set",
|
|
"global.index-url",
|
|
repository_url,
|
|
],
|
|
capture_output=True,
|
|
env=get_env(),
|
|
)
|
|
|
|
if result.returncode != 0:
|
|
logger.error(
|
|
f"Failed to configure PIP global settings: {result.stderr.decode('utf-8')}"
|
|
)
|
|
return None
|
|
|
|
output = result.stdout.decode("utf-8")
|
|
match = re.search(r"Writing to (.+)", output)
|
|
|
|
if match:
|
|
config_file_path = match.group(1).strip()
|
|
return Path(config_file_path)
|
|
|
|
logger.error("Failed to match the config file path written by pip.")
|
|
return Path()
|
|
except Exception:
|
|
logger.exception("Failed to configure PIP global settings.")
|
|
|
|
return None
|
|
|
|
@classmethod
|
|
def reset_system(cls, console: Console = main_console):
|
|
# TODO: Move this logic and implement it in a more robust way
|
|
try:
|
|
subprocess.run(
|
|
[
|
|
get_unwrapped_command(name="pip"),
|
|
"config",
|
|
"--user",
|
|
"unset",
|
|
"global.index-url",
|
|
],
|
|
capture_output=True,
|
|
env=get_env(),
|
|
)
|
|
except Exception:
|
|
console.print("Failed to reset PIP global settings.")
|
|
|
|
@classmethod
|
|
def default_index_url(cls) -> str:
|
|
return "https://pypi.org/simple/"
|
|
|
|
@classmethod
|
|
def build_index_url(cls, ctx: typer.Context, index_url: Optional[str]) -> str:
|
|
return build_index_url(ctx, index_url, "pypi")
|