Skip to content

Commit 03a4166

Browse files
authored
Merge branch 'main' into feature/add-support-for-manually-triggering-documentation-build
2 parents cf27eb8 + dec6bd3 commit 03a4166

File tree

12 files changed

+537
-78
lines changed

12 files changed

+537
-78
lines changed

.github/workflows/report.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,4 @@ jobs:
5151
poetry run coverage report -- --format markdown >> $GITHUB_STEP_SUMMARY
5252
echo -e "\n\n# Static Code Analysis\n" >> $GITHUB_STEP_SUMMARY
5353
cat .lint.txt >> $GITHUB_STEP_SUMMARY
54+
poetry run tbx security pretty-print .security.json >> $GITHUB_STEP_SUMMARY

doc/changes/unreleased.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
# Unreleased
22

33
## ✨ Added
4+
45
* Added support to manually trigger documentation build
6+
* #248: Added security results to workflow summary
7+
* #233: Added nox task to verify dependency declarations

doc/user_guide/getting_started.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ You are ready to use the toolbox. With *nox -l* you can list all available tasks
193193
- lint:code -> Runs the static code analyzer on the project
194194
- lint:typing -> Runs the type checker on the project
195195
- lint:security -> Runs the security linter on the project
196+
- lint:dependencies -> Checks if only valid sources of dependencies are used
196197
- docs:multiversion -> Builds the multiversion project documentation
197198
- docs:build -> Builds the project documentation
198199
- docs:open -> Opens the built project documentation

exasol/toolbox/nox/_lint.py

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
11
from __future__ import annotations
22

3-
from typing import Iterable
3+
from typing import (
4+
Iterable,
5+
List,
6+
Dict
7+
)
48

59
import nox
610
from nox import Session
711

812
from exasol.toolbox.nox._shared import python_files
913
from noxconfig import PROJECT_CONFIG
1014

15+
from pathlib import Path
16+
import rich.console
17+
import tomlkit
18+
import sys
19+
1120

1221
def _pylint(session: Session, files: Iterable[str]) -> None:
1322
session.run(
@@ -65,6 +74,61 @@ def _security_lint(session: Session, files: Iterable[str]) -> None:
6574
)
6675

6776

77+
class Dependencies:
78+
def __init__(self, illegal: Dict[str, List[str]] | None):
79+
self._illegal = illegal or {}
80+
81+
@staticmethod
82+
def parse(pyproject_toml: str) -> "Dependencies":
83+
def _source_filter(version) -> bool:
84+
ILLEGAL_SPECIFIERS = ['url', 'git', 'path']
85+
return any(
86+
specifier in version
87+
for specifier in ILLEGAL_SPECIFIERS
88+
)
89+
90+
def find_illegal(part) -> List[str]:
91+
return [
92+
f"{name} = {version}"
93+
for name, version in part.items()
94+
if _source_filter(version)
95+
]
96+
97+
illegal: Dict[str, List[str]] = {}
98+
toml = tomlkit.loads(pyproject_toml)
99+
poetry = toml.get("tool", {}).get("poetry", {})
100+
101+
part = poetry.get("dependencies", {})
102+
if illegal_group := find_illegal(part):
103+
illegal["tool.poetry.dependencies"] = illegal_group
104+
105+
part = poetry.get("dev", {}).get("dependencies", {})
106+
if illegal_group := find_illegal(part):
107+
illegal["tool.poetry.dev.dependencies"] = illegal_group
108+
109+
part = poetry.get("group", {})
110+
for group, content in part.items():
111+
illegal_group = find_illegal(content.get("dependencies", {}))
112+
if illegal_group:
113+
illegal[f"tool.poetry.group.{group}.dependencies"] = illegal_group
114+
return Dependencies(illegal)
115+
116+
@property
117+
def illegal(self) -> Dict[str, List[str]]:
118+
return self._illegal
119+
120+
121+
def report_illegal(illegal: Dict[str, List[str]], console: rich.console.Console):
122+
count = sum(len(deps) for deps in illegal.values())
123+
suffix = "y" if count == 1 else "ies"
124+
console.print(f"{count} illegal dependenc{suffix}\n", style="red")
125+
for section, dependencies in illegal.items():
126+
console.print(f"\\[{section}]", style="red")
127+
for dependency in dependencies:
128+
console.print(dependency, style="red")
129+
console.print("")
130+
131+
68132
@nox.session(name="lint:code", python=False)
69133
def lint(session: Session) -> None:
70134
"Runs the static code analyzer on the project"
@@ -84,3 +148,14 @@ def security_lint(session: Session) -> None:
84148
"""Runs the security linter on the project"""
85149
py_files = [f"{file}" for file in python_files(PROJECT_CONFIG.root)]
86150
_security_lint(session, list(filter(lambda file: "test" not in file, py_files)))
151+
152+
153+
@nox.session(name="lint:dependencies", python=False)
154+
def dependency_check(session: Session) -> None:
155+
"""Checks if only valid sources of dependencies are used"""
156+
content = Path(PROJECT_CONFIG.root, "pyproject.toml").read_text()
157+
dependencies = Dependencies.parse(content)
158+
console = rich.console.Console()
159+
if illegal := dependencies.illegal:
160+
report_illegal(illegal, console)
161+
sys.exit(1)

exasol/toolbox/nox/tasks.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,5 +68,6 @@ def check(session: Session) -> None:
6868
python_files,
6969
)
7070

71+
7172
# isort: on
7273
# fmt: on

exasol/toolbox/templates/github/workflows/report.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,4 @@ jobs:
5151
poetry run coverage report -- --format markdown >> $GITHUB_STEP_SUMMARY
5252
echo -e "\n\n# Static Code Analysis\n" >> $GITHUB_STEP_SUMMARY
5353
cat .lint.txt >> $GITHUB_STEP_SUMMARY
54+
poetry run tbx security pretty-print .security.json >> $GITHUB_STEP_SUMMARY

exasol/toolbox/tools/security.py

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616
Iterable,
1717
Tuple,
1818
)
19-
2019
import typer
20+
from pathlib import Path
2121

2222
stdout = print
2323
stderr = partial(print, file=sys.stderr)
@@ -101,6 +101,64 @@ def from_maven(report: str) -> Iterable[Issue]:
101101
)
102102

103103

104+
@dataclass(frozen=True)
105+
class SecurityIssue:
106+
file_name: str
107+
line: int
108+
column: int
109+
cwe: str
110+
test_id: str
111+
description: str
112+
references: tuple
113+
114+
115+
def from_json(report_str: str, prefix: Path) -> Iterable[SecurityIssue]:
116+
report = json.loads(report_str)
117+
issues = report.get("results", {})
118+
for issue in issues:
119+
references = []
120+
if issue["more_info"]:
121+
references.append(issue["more_info"])
122+
if issue.get("issue_cwe", {}).get("link", None):
123+
references.append(issue["issue_cwe"]["link"])
124+
yield SecurityIssue(
125+
file_name=issue["filename"].replace(str(prefix) + "/", ""),
126+
line=issue["line_number"],
127+
column=issue["col_offset"],
128+
cwe=str(issue["issue_cwe"].get("id", "")),
129+
test_id=issue["test_id"],
130+
description=issue["issue_text"],
131+
references=tuple(references)
132+
)
133+
134+
135+
def issues_to_markdown(issues: Iterable[SecurityIssue]) -> str:
136+
template = cleandoc("""
137+
{header}{rows}
138+
""")
139+
140+
def _header():
141+
header = "# Security\n\n"
142+
header += "|File|line/<br>column|Cwe|Test ID|Details|\n"
143+
header += "|---|:-:|:-:|:-:|---|\n"
144+
return header
145+
146+
def _row(issue):
147+
row = "|" + issue.file_name + "|"
148+
row += f"line: {issue.line}<br>column: {issue.column}|"
149+
row += issue.cwe + "|"
150+
row += issue.test_id + "|"
151+
for element in issue.references:
152+
row += element + " ,<br>"
153+
row = row[:-5] + "|"
154+
return row
155+
156+
return template.format(
157+
header=_header(),
158+
rows="\n".join(_row(i) for i in issues)
159+
)
160+
161+
104162
def security_issue_title(issue: Issue) -> str:
105163
return f"🔐 {issue.cve}: {issue.coordinates}"
106164

@@ -159,7 +217,6 @@ def create_security_issue(issue: Issue, project="") -> Tuple[str, str]:
159217
CVE_CLI = typer.Typer()
160218
CLI.add_typer(CVE_CLI, name="cve", help="Work with CVE's")
161219

162-
163220
class Format(str, Enum):
164221
Maven = "maven"
165222

@@ -257,6 +314,21 @@ def create(
257314
stdout(format_jsonl(issue_url, issue))
258315

259316

317+
class PPrintFormats(str, Enum):
318+
markdown = "markdown"
319+
320+
321+
@CLI.command(name="pretty-print")
322+
def json_issue_to_markdown(
323+
json_file: typer.FileText = typer.Argument(mode="r", help="json file with issues to convert"),
324+
path: Path = typer.Argument(default=Path("."), help="path to project root")
325+
) -> None:
326+
content = json_file.read()
327+
issues = from_json(content, path.absolute())
328+
issues = sorted(issues, key=lambda i: (i.file_name, i.cwe, i.test_id))
329+
print(issues_to_markdown(issues))
330+
331+
260332
def format_jsonl(issue_url: str, issue: Issue) -> str:
261333
issue_json = asdict(issue)
262334
issue_json["issue_url"] = issue_url.strip()

0 commit comments

Comments
 (0)