Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"

[tool.poetry]
name = "codeql-wrapper"
version = "0.2.0-alpha.2"
version = "0.2.0-alpha.5"
description = "A universal Python CLI wrapper for running CodeQL analysis on any type of project (monorepo or single repository) across different CI/CD platforms including Jenkins, GitHub Actions, Harness, and any environment where Python scripts can be executed."
authors = ["Mateus Perdigão Domiciano <mateus.domiciano@moduscreate.com>", "Fernando Matsuo Santos <fernando.matsuo@moduscreate.com>"]
license = "MIT"
Expand Down
149 changes: 47 additions & 102 deletions src/codeql_wrapper/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,14 +121,8 @@ def cli(ctx: click.Context, verbose: bool = False) -> None:
)
@click.option(
"--base-ref",
default="HEAD~1",
help="Base Git reference to compare changes from (default: HEAD~1)",
)
@click.option(
"--target-ref",
default="HEAD",
help="Target Git reference to compare changes to (default: HEAD)",
)
@click.pass_context
def analyze(
ctx: click.Context,
Expand All @@ -145,7 +139,6 @@ def analyze(
max_workers: Optional[int],
only_changed_files: bool,
base_ref: str,
target_ref: str,
) -> None:
"""
Run CodeQL analysis on a repository.
Expand All @@ -156,26 +149,20 @@ def analyze(
logger = get_logger(__name__)
verbose = ctx.obj.get("verbose", False)

# If monorepo mode and .codeql.json exists, default repository_path to current directory
root_config_path = Path(repository_path) / ".codeql.json"
if monorepo and root_config_path.exists():
logger.info(
"Detected .codeql.json in root. Using current directory as repository path."
)

logger.info(f"Starting CodeQL analysis for: {repository_path}")

# Try to detect Git information automatically if not provided
git_info = GitUtils.get_git_info(
repository_path=Path(repository_path),
base_ref=base_ref,
commit_sha=commit_sha,
current_ref=ref,
repository=repository,
)

# Validate upload-sarif parameters if upload is requested
if upload_sarif:
# Try to detect Git information automatically if not provided
git_info = GitUtils.get_git_info(Path(repository_path))

# Use provided values or fall back to Git detection
final_repository = repository or git_info.repository
final_commit_sha = commit_sha or git_info.commit_sha
final_ref = ref or git_info.ref

if not final_repository:
if not git_info.repository:
click.echo(
click.style("ERROR:", fg="red", bold=True)
+ " --repository is required when using --upload-sarif. "
Expand All @@ -184,7 +171,7 @@ def analyze(
)
sys.exit(1)

if not final_commit_sha:
if not git_info.commit_sha:
click.echo(
click.style("ERROR:", fg="red", bold=True)
+ " --commit-sha is required when using --upload-sarif. "
Expand All @@ -203,11 +190,6 @@ def analyze(
)
sys.exit(1)

# Update variables for later use
repository = final_repository
commit_sha = final_commit_sha
ref = final_ref

# Parse target languages if provided
target_languages = None
if languages:
Expand Down Expand Up @@ -241,16 +223,19 @@ def analyze(
)

# Validate only-changed-files option
if only_changed_files:
if not GitUtils.is_git_repository(Path(repository_path)):
if only_changed_files and not git_info.is_git_repository:
click.echo(
click.style("ERROR:", fg="red", bold=True)
+ " --only-changed-files requires a Git repository"
)
sys.exit(1)

if git_info.base_ref is None:
click.echo(
click.style("ERROR:", fg="red", bold=True)
+ " --only-changed-files requires a Git repository"
+ "No base reference provided. Please use --base-ref option to specify it."
)
sys.exit(1)
logger.info(
f"Filtering projects based on changes between {base_ref} and {target_ref}"
)

# Create analysis request
request = CodeQLAnalysisRequest(
Expand All @@ -262,8 +247,7 @@ def analyze(
monorepo=monorepo,
max_workers=max_workers,
only_changed_files=only_changed_files,
base_ref=base_ref,
target_ref=target_ref,
git_info=git_info,
)

# Execute analysis
Expand Down Expand Up @@ -317,28 +301,25 @@ def analyze(
+ " No SARIF files found for upload"
)
else:
# These are guaranteed to be non-None due to validation above
assert repository is not None
assert commit_sha is not None
assert github_token is not None

# Show upload info
used_ref = ref or "refs/heads/main"
click.echo(
"\n"
+ click.style("UPLOADING:", fg="blue", bold=True)
+ f" {len(sarif_files)} SARIF file(s) to {repository}"
)
click.echo(f" Commit: {commit_sha}")
click.echo(f" Reference: {used_ref}")
click.echo(f" Reference: {git_info.current_ref}")

# Create upload request
assert git_info.repository is not None
assert git_info.commit_sha is not None
assert github_token is not None

upload_request = SarifUploadRequest(
sarif_files=sarif_files,
repository=repository,
commit_sha=commit_sha,
repository=git_info.repository,
commit_sha=git_info.commit_sha,
github_token=github_token,
ref=ref,
ref=git_info.current_ref,
)

# Execute upload
Expand Down Expand Up @@ -471,7 +452,7 @@ def install(ctx: click.Context, version: str, force: bool) -> None:
def upload_sarif(
ctx: click.Context,
sarif_file: str,
repository: Optional[str],
repository: str,
commit_sha: Optional[str],
ref: Optional[str],
github_token: Optional[str],
Expand All @@ -491,32 +472,12 @@ def upload_sarif(
logger = get_logger(__name__)

# Try to detect Git information automatically if not provided
sarif_file_path = Path(sarif_file)
# Try to find Git repository by looking at the SARIF file's directory and parents
git_repo_path = sarif_file_path.parent
while git_repo_path != git_repo_path.parent:
if GitUtils.is_git_repository(git_repo_path):
break
git_repo_path = git_repo_path.parent
else:
# If no Git repo found, use current directory
git_repo_path = Path.cwd()

git_info = GitUtils.get_git_info(git_repo_path)

# Use provided values or fall back to Git detection
final_repository = repository or git_info.repository
final_commit_sha = commit_sha or git_info.commit_sha
final_ref = ref or git_info.ref

# Debug output (temporary)
click.echo(
f"Debug: repository={repository}, git_info.repository={git_info.repository}"
git_info = GitUtils.get_git_info(
commit_sha=commit_sha, current_ref=ref, repository=repository
)
click.echo(f"Debug: final_repository={final_repository}")

# Parse repository owner/name
if not final_repository:
if not repository:
click.echo(
click.style("ERROR:", fg="red", bold=True)
+ " Repository is required. Provide --repository or ensure you're in a Git "
Expand All @@ -525,21 +486,19 @@ def upload_sarif(
)
sys.exit(1)

if not final_commit_sha:
if not git_info.repository:
click.echo(
click.style("ERROR:", fg="red", bold=True)
+ " Commit SHA is required. Provide --commit-sha or ensure you're in a Git "
"repository.",
+ " Invalid repository format. Use 'owner/name' format.",
err=True,
)
sys.exit(1)

try:
repository_owner, repository_name = final_repository.split("/", 1)
except ValueError:
if not git_info.commit_sha:
click.echo(
click.style("ERROR:", fg="red", bold=True)
+ " Invalid repository format. Use 'owner/name' format.",
+ " Commit SHA is required. Provide --commit-sha or ensure you're in a Git "
"repository.",
err=True,
)
sys.exit(1)
Expand All @@ -554,37 +513,21 @@ def upload_sarif(
)
sys.exit(1)

used_ref = final_ref or "refs/heads/main"
click.echo(
click.style("UPLOADING:", fg="blue", bold=True)
+ f" SARIF file: {sarif_file}"
)
click.echo(f" Repository: {final_repository}")
click.echo(f" Commit: {final_commit_sha}")
click.echo(f" Reference: {used_ref}")

# Show auto-detected information
if git_info.repository or git_info.commit_sha or git_info.ref:
auto_detected = []
if not repository and git_info.repository:
auto_detected.append("repository")
if not commit_sha and git_info.commit_sha:
auto_detected.append("commit-sha")
if not ref and git_info.ref:
auto_detected.append("ref")
if auto_detected:
click.echo(
click.style("INFO:", fg="cyan", bold=True)
+ f" Auto-detected: {', '.join(auto_detected)}"
)
click.echo(f" Repository: {git_info.repository}")
click.echo(f" Commit: {git_info.commit_sha}")
click.echo(f" Reference: {git_info.current_ref}")

# Create upload request
upload_request = SarifUploadRequest(
sarif_files=[Path(sarif_file)],
repository=final_repository,
commit_sha=final_commit_sha,
repository=git_info.repository,
commit_sha=git_info.commit_sha,
github_token=github_token,
ref=final_ref,
ref=git_info.current_ref,
)

# Execute upload
Expand All @@ -603,7 +546,9 @@ def upload_sarif(
click.echo(click.style("ERROR:", fg="red", bold=True) + f" {error}")
raise Exception("SARIF upload failed")

logger.info(f"SARIF upload completed for {final_repository}")
logger.info(
f"SARIF upload completed for {git_info.repository} at {git_info.commit_sha}"
)

except Exception as e:
logger = get_logger(__name__)
Expand Down
5 changes: 3 additions & 2 deletions src/codeql_wrapper/domain/entities/codeql_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from typing import List, Optional, Set
from datetime import datetime

from codeql_wrapper.infrastructure.git_utils import GitInfo


class CodeQLLanguage(Enum):
"""Supported CodeQL languages."""
Expand Down Expand Up @@ -77,8 +79,7 @@ class CodeQLAnalysisRequest:
queries: Optional[List[str]] = None
max_workers: Optional[int] = None
only_changed_files: bool = False
base_ref: str = "HEAD~1"
target_ref: str = "HEAD"
git_info: GitInfo = field(default_factory=GitInfo)

def __post_init__(self) -> None:
"""Validate analysis request."""
Expand Down
23 changes: 13 additions & 10 deletions src/codeql_wrapper/domain/use_cases/codeql_analysis_use_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,14 @@ def execute(self, request: CodeQLAnalysisRequest) -> RepositoryAnalysisSummary:
)

# Step 3: Detect projects
configFile = (
request.repository_path / ".codeql.json" if request.monorepo else None
)
config_data = None
if request.monorepo and configFile and configFile.exists():
with open(configFile, "r", encoding="utf-8") as f:
root_config_path = Path(request.repository_path, ".codeql.json")
if request.monorepo and root_config_path.exists():
self._logger.info(
"Detected .codeql.json in root. Using current directory as repository path."
)

with open(root_config_path, "r", encoding="utf-8") as f:
config_data = json.load(f)

projects: List[ProjectInfo] = self._detect_projects(
Expand Down Expand Up @@ -378,22 +380,23 @@ def _detect_projects(
# Get changed files if filtering is enabled
changed_files = []
if request.only_changed_files:
if GitUtils.is_git_repository(request.repository_path):
if request.git_info.base_ref and request.git_info.current_ref:
self._logger.info(
f"Filtering projects based on changed files between "
f"{request.base_ref} and {request.target_ref}"
f"{request.git_info.base_ref} and {request.git_info.current_ref}"
)
changed_files = GitUtils.get_diff_files(
request.repository_path,
base_ref=request.base_ref,
target_ref=request.target_ref,
base_ref=request.git_info.base_ref,
target_ref=request.git_info.current_ref,
)
self._logger.debug(
f"Found {len(changed_files)} changed files: {changed_files}"
)
else:
self._logger.warning(
"Cannot filter projects by changed files: not a Git repository"
"Changed files filtering is enabled but no base or current ref provided. "
"All projects will be included."
)

if isMonorepo:
Expand Down
Loading
Loading