Skip to content

Commit 629a500

Browse files
committed
github scm passing tests; time for refactor
1 parent c2f4b45 commit 629a500

File tree

12 files changed

+466
-107
lines changed

12 files changed

+466
-107
lines changed

pyproject.toml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,14 @@ exclude = [
110110
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
111111
# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or
112112
# McCabe complexity (`C901`) by default.
113-
select = ["E4", "E7", "E9", "F"]
114-
ignore = []
113+
select = [
114+
"E4", "E7", "E9", "F", # Current rules
115+
"I", # isort
116+
"F401", # Unused imports
117+
"F403", # Star imports
118+
"F405", # Star imports undefined
119+
"F821", # Undefined names
120+
]
115121

116122
# Allow fix for all enabled rules (when `--fix`) is provided.
117123
fixable = ["ALL"]
@@ -120,6 +126,9 @@ unfixable = []
120126
# Allow unused variables when underscore-prefixed.
121127
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
122128

129+
[tool.ruff.lint.isort]
130+
known-first-party = ["socketsecurity"]
131+
123132
[tool.ruff.format]
124133
# Like Black, use double quotes for strings.
125134
quote-style = "double"

socketsecurity/core/__init__.py

Lines changed: 44 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,30 @@
1+
import base64
2+
import json
13
import logging
4+
import platform
5+
import time
6+
from glob import glob
27
from pathlib import PurePath
3-
from .utils import socket_globs
4-
from .config import SocketConfig
5-
from .client import CliClient
6-
import requests
78
from urllib.parse import urlencode
8-
import base64
9-
import json
9+
10+
import requests
11+
12+
from socketsecurity import __version__
13+
from socketsecurity.core.classes import Alert, Diff, FullScan, FullScanParams, Issue, Package, Purl, Report, Repository
1014
from socketsecurity.core.exceptions import (
11-
APIFailure, APIKeyMissing, APIAccessDenied, APIInsufficientQuota, APIResourceNotFound, APICloudflareError
15+
APIAccessDenied,
16+
APICloudflareError,
17+
APIFailure,
18+
APIInsufficientQuota,
19+
APIKeyMissing,
20+
APIResourceNotFound,
1221
)
13-
from socketsecurity import __version__
14-
from socketsecurity.core.licenses import Licenses
1522
from socketsecurity.core.issues import AllIssues
16-
from socketsecurity.core.classes import (
17-
Report,
18-
Issue,
19-
Package,
20-
Alert,
21-
FullScan,
22-
FullScanParams,
23-
Repository,
24-
Diff,
25-
Purl
26-
)
27-
import platform
28-
from glob import glob
29-
import time
23+
from socketsecurity.core.licenses import Licenses
24+
25+
from .cli_client import CliClient
26+
from .config import SocketConfig
27+
from .utils import socket_globs
3028

3129
__all__ = [
3230
"Core",
@@ -270,6 +268,11 @@ def create_sbom_output(self, diff: Diff) -> dict:
270268
# TODO: verify what this does. It looks like it should be named "all_files_unsupported"
271269
@staticmethod
272270
def match_supported_files(files: list) -> bool:
271+
"""
272+
Checks if any of the files in the list match the supported file patterns
273+
Returns True if NO files match (meaning no changes to manifest files)
274+
Returns False if ANY files match (meaning there are manifest changes)
275+
"""
273276
matched_files = []
274277
not_matched = False
275278
for ecosystem in socket_globs:
@@ -724,3 +727,21 @@ def save_file(file_name: str, content: str) -> None:
724727
# output = []
725728
# for package_id in diff.packages:
726729
# purl = Core.create_purl(package_id, diff.packages)
730+
731+
@staticmethod
732+
def has_manifest_files(files: list) -> bool:
733+
"""
734+
Checks if any files in the list are supported manifest files.
735+
Returns True if ANY files match our manifest patterns (meaning we need to scan)
736+
Returns False if NO files match (meaning we can skip scanning)
737+
"""
738+
for ecosystem in socket_globs:
739+
patterns = socket_globs[ecosystem]
740+
for file_name in patterns:
741+
pattern = patterns[file_name]["pattern"]
742+
for file in files:
743+
if "\\" in file:
744+
file = file.replace("\\", "/")
745+
if PurePath(file).match(pattern):
746+
return True # Found a manifest file, no need to check further
747+
return False # No manifest files found
File renamed without changes.

socketsecurity/core/scm/__init__.py

Whitespace-only changes.

socketsecurity/core/scm/base.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from abc import ABC, abstractmethod
2+
from typing import Dict
3+
4+
from ..classes import Comment
5+
from .client import ScmClient
6+
7+
8+
class SCM(ABC):
9+
def __init__(self, client: ScmClient):
10+
self.client = client
11+
12+
@abstractmethod
13+
def check_event_type(self) -> str:
14+
"""Determine the type of event (push, pr, comment)"""
15+
pass
16+
17+
@abstractmethod
18+
def add_socket_comments(
19+
self,
20+
security_comment: str,
21+
overview_comment: str,
22+
comments: Dict[str, Comment],
23+
new_security_comment: bool = True,
24+
new_overview_comment: bool = True
25+
) -> None:
26+
"""Add or update comments on PR"""
27+
pass
28+
29+
@abstractmethod
30+
def get_comments_for_pr(self, repo: str, pr: str) -> Dict[str, Comment]:
31+
"""Get existing comments for PR"""
32+
pass
33+
34+
@abstractmethod
35+
def remove_comment_alerts(self, comments: Dict[str, Comment]) -> None:
36+
"""Process and remove alerts from comments"""
37+
pass

socketsecurity/core/scm/client.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from abc import abstractmethod
2+
from typing import Dict
3+
4+
from ..cli_client import CliClient
5+
6+
7+
class ScmClient(CliClient):
8+
def __init__(self, token: str, api_url: str):
9+
self.token = token
10+
self.api_url = api_url
11+
12+
@abstractmethod
13+
def get_headers(self) -> Dict:
14+
"""Each SCM implements its own auth headers"""
15+
pass
16+
17+
def request(self, path: str, **kwargs):
18+
"""Override base request to use SCM-specific headers and base_url"""
19+
headers = kwargs.pop('headers', None) or self.get_headers()
20+
return super().request(
21+
path=path,
22+
headers=headers,
23+
base_url=self.api_url,
24+
**kwargs
25+
)
26+
27+
class GithubClient(ScmClient):
28+
def get_headers(self) -> Dict:
29+
return {
30+
'Authorization': f"Bearer {self.token}",
31+
'User-Agent': 'SocketPythonScript/0.0.1',
32+
"accept": "application/json"
33+
}
34+
35+
class GitlabClient(ScmClient):
36+
def get_headers(self) -> Dict:
37+
return {
38+
'Authorization': f"Bearer {self.token}",
39+
'User-Agent': 'SocketPythonScript/0.0.1',
40+
"accept": "application/json"
41+
}

socketsecurity/core/github.py renamed to socketsecurity/core/scm/github.py

Lines changed: 76 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,33 @@
11
import json
22
import os
3-
from socketsecurity.core import log, do_request
4-
import requests
5-
from socketsecurity.core.classes import Comment
6-
from socketsecurity.core.scm_comments import Comments
73
import sys
4+
from dataclasses import dataclass
85

6+
from git import Optional
7+
8+
from socketsecurity.core import do_request, log
9+
from socketsecurity.core.classes import Comment
10+
from socketsecurity.core.scm_comments import Comments
911

10-
global github_sha
11-
global github_api_url
12-
global github_ref_type
13-
global github_event_name
14-
global github_workspace
15-
global github_repository
16-
global github_ref_name
17-
global github_actor
18-
global default_branch
19-
global github_env
20-
global pr_number
21-
global pr_name
22-
global is_default_branch
23-
global commit_message
24-
global committer
25-
global gh_api_token
26-
global github_repository_owner
27-
global event_action
12+
# Declare all globals with initial None values
13+
github_sha: Optional[str] = None
14+
github_api_url: Optional[str] = None
15+
github_ref_type: Optional[str] = None
16+
github_event_name: Optional[str] = None
17+
github_workspace: Optional[str] = None
18+
github_repository: Optional[str] = None
19+
github_ref_name: Optional[str] = None
20+
github_actor: Optional[str] = None
21+
default_branch: Optional[str] = None
22+
github_env: Optional[str] = None
23+
pr_number: Optional[str] = None
24+
pr_name: Optional[str] = None
25+
is_default_branch: bool = False
26+
commit_message: Optional[str] = None
27+
committer: Optional[str] = None
28+
gh_api_token: Optional[str] = None
29+
github_repository_owner: Optional[str] = None
30+
event_action: Optional[str] = None
2831

2932
github_variables = [
3033
"GITHUB_SHA",
@@ -45,11 +48,58 @@
4548
"EVENT_ACTION"
4649
]
4750

51+
@dataclass
52+
class GithubConfig:
53+
"""Configuration from GitHub environment variables"""
54+
sha: str
55+
api_url: str
56+
ref_type: str
57+
event_name: str
58+
workspace: str
59+
repository: str
60+
ref_name: str
61+
default_branch: bool
62+
pr_number: Optional[str]
63+
pr_name: Optional[str]
64+
commit_message: Optional[str]
65+
actor: str
66+
env: str
67+
token: str
68+
owner: str
69+
event_action: Optional[str]
70+
71+
@classmethod
72+
def from_env(cls) -> 'GithubConfig':
73+
"""Create config from environment variables"""
74+
token = os.getenv('GH_API_TOKEN')
75+
if not token:
76+
log.error("Unable to get Github API Token from GH_API_TOKEN")
77+
sys.exit(2)
78+
79+
return cls(
80+
sha=os.getenv('GITHUB_SHA', ''),
81+
api_url=os.getenv('GITHUB_API_URL', ''),
82+
ref_type=os.getenv('GITHUB_REF_TYPE', ''),
83+
event_name=os.getenv('GITHUB_EVENT_NAME', ''),
84+
workspace=os.getenv('GITHUB_WORKSPACE', ''),
85+
repository=os.getenv('GITHUB_REPOSITORY', '').split('/')[-1],
86+
ref_name=os.getenv('GITHUB_REF_NAME', ''),
87+
default_branch=os.getenv('DEFAULT_BRANCH', '').lower() == 'true',
88+
pr_number=os.getenv('PR_NUMBER'),
89+
pr_name=os.getenv('PR_NAME'),
90+
commit_message=os.getenv('COMMIT_MESSAGE'),
91+
actor=os.getenv('GITHUB_ACTOR', ''),
92+
env=os.getenv('GITHUB_ENV', ''),
93+
token=token,
94+
owner=os.getenv('GITHUB_REPOSITORY_OWNER', ''),
95+
event_action=os.getenv('EVENT_ACTION')
96+
)
97+
98+
4899
for env in github_variables:
49100
var_name = env.lower()
50101
globals()[var_name] = os.getenv(env) or None
51102
if var_name == "default_branch":
52-
global is_default_branch
53103
if default_branch is None or default_branch.lower() == "false":
54104
is_default_branch = False
55105
else:
@@ -233,15 +283,14 @@ def post_reaction(comment_id: int) -> None:
233283
def comment_reaction_exists(comment_id: int) -> bool:
234284
repo = github_repository.rsplit("/", 1)[1]
235285
path = f"repos/{github_repository_owner}/{repo}/issues/comments/{comment_id}/reactions"
236-
response = do_request(path, headers=headers, base_url=github_api_url)
237-
exists = False
238286
try:
287+
response = do_request(path, headers=headers, base_url=github_api_url)
239288
data = response.json()
240289
for reaction in data:
241290
content = reaction.get("content")
242291
if content is not None and content == ":thumbsup:":
243-
exists = True
292+
return True
244293
except Exception as error:
245294
log.error(f"Unable to get reaction for {comment_id} for PR {pr_number}")
246295
log.error(error)
247-
return exists
296+
return False
File renamed without changes.

0 commit comments

Comments
 (0)