iam-policy-validator 1.10.3__py3-none-any.whl → 1.11.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- iam_policy_validator-1.11.1.dist-info/METADATA +782 -0
- {iam_policy_validator-1.10.3.dist-info → iam_policy_validator-1.11.1.dist-info}/RECORD +25 -21
- iam_validator/__version__.py +1 -1
- iam_validator/checks/action_condition_enforcement.py +27 -14
- iam_validator/checks/sensitive_action.py +123 -11
- iam_validator/checks/utils/policy_level_checks.py +47 -10
- iam_validator/commands/__init__.py +6 -0
- iam_validator/commands/completion.py +467 -0
- iam_validator/commands/query.py +485 -0
- iam_validator/commands/validate.py +21 -26
- iam_validator/core/config/category_suggestions.py +77 -0
- iam_validator/core/config/condition_requirements.py +105 -54
- iam_validator/core/config/defaults.py +82 -6
- iam_validator/core/config/wildcards.py +3 -0
- iam_validator/core/diff_parser.py +321 -0
- iam_validator/core/formatters/enhanced.py +34 -27
- iam_validator/core/models.py +2 -0
- iam_validator/core/pr_commenter.py +179 -51
- iam_validator/core/report.py +19 -17
- iam_validator/integrations/github_integration.py +250 -1
- iam_validator/sdk/__init__.py +33 -0
- iam_validator/sdk/query_utils.py +454 -0
- iam_policy_validator-1.10.3.dist-info/METADATA +0 -549
- {iam_policy_validator-1.10.3.dist-info → iam_policy_validator-1.11.1.dist-info}/WHEEL +0 -0
- {iam_policy_validator-1.10.3.dist-info → iam_policy_validator-1.11.1.dist-info}/entry_points.txt +0 -0
- {iam_policy_validator-1.10.3.dist-info → iam_policy_validator-1.11.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -61,7 +61,6 @@ class EnhancedFormatter(OutputFormatter):
|
|
|
61
61
|
)
|
|
62
62
|
|
|
63
63
|
# Header with title
|
|
64
|
-
console.print()
|
|
65
64
|
title = Text(
|
|
66
65
|
f"IAM Policy Validation Report (v{__version__})",
|
|
67
66
|
style="bold cyan",
|
|
@@ -75,7 +74,6 @@ class EnhancedFormatter(OutputFormatter):
|
|
|
75
74
|
width=constants.CONSOLE_PANEL_WIDTH,
|
|
76
75
|
)
|
|
77
76
|
)
|
|
78
|
-
console.print()
|
|
79
77
|
|
|
80
78
|
# Executive Summary with progress bars (optional)
|
|
81
79
|
if show_summary:
|
|
@@ -83,12 +81,15 @@ class EnhancedFormatter(OutputFormatter):
|
|
|
83
81
|
|
|
84
82
|
# Severity breakdown if there are issues (optional)
|
|
85
83
|
if show_severity_breakdown and report.total_issues > 0:
|
|
86
|
-
console.print()
|
|
87
84
|
self._print_severity_breakdown(console, report)
|
|
88
85
|
|
|
89
|
-
console.print(
|
|
90
|
-
|
|
91
|
-
|
|
86
|
+
console.print(
|
|
87
|
+
Rule(
|
|
88
|
+
title="[bold]Detailed Results",
|
|
89
|
+
style=constants.CONSOLE_HEADER_COLOR,
|
|
90
|
+
),
|
|
91
|
+
width=constants.CONSOLE_PANEL_WIDTH,
|
|
92
|
+
)
|
|
92
93
|
|
|
93
94
|
# Detailed results using tree structure
|
|
94
95
|
for idx, result in enumerate(report.results, 1):
|
|
@@ -99,7 +100,7 @@ class EnhancedFormatter(OutputFormatter):
|
|
|
99
100
|
self._print_final_status(console, report)
|
|
100
101
|
|
|
101
102
|
# Get the formatted output
|
|
102
|
-
output = string_buffer.getvalue()
|
|
103
|
+
output = string_buffer.getvalue().rstrip("\n")
|
|
103
104
|
string_buffer.close()
|
|
104
105
|
|
|
105
106
|
return output
|
|
@@ -305,13 +306,10 @@ class EnhancedFormatter(OutputFormatter):
|
|
|
305
306
|
if not result.issues:
|
|
306
307
|
console.print(header)
|
|
307
308
|
console.print(" [dim italic]No issues detected[/dim italic]")
|
|
308
|
-
console.print()
|
|
309
309
|
return
|
|
310
310
|
|
|
311
311
|
console.print(header)
|
|
312
312
|
console.print(f" [dim]{len(result.issues)} issue(s) found[/dim]")
|
|
313
|
-
console.print()
|
|
314
|
-
|
|
315
313
|
# Create tree structure for issues
|
|
316
314
|
tree = Tree(f"[bold]Issues ({len(result.issues)})[/bold]", guide_style="bright_black")
|
|
317
315
|
|
|
@@ -372,10 +370,14 @@ class EnhancedFormatter(OutputFormatter):
|
|
|
372
370
|
def _add_issue_to_tree(self, branch: Tree, issue, color: str) -> None:
|
|
373
371
|
"""Add an issue to a tree branch."""
|
|
374
372
|
# Build location string (use 1-indexed statement numbers for user-facing output)
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
373
|
+
# Handle policy-level issues (statement_index = -1)
|
|
374
|
+
if issue.statement_index == -1:
|
|
375
|
+
location = "Policy-level"
|
|
376
|
+
else:
|
|
377
|
+
statement_num = issue.statement_index + 1
|
|
378
|
+
location = f"Statement {statement_num}"
|
|
379
|
+
if issue.statement_sid:
|
|
380
|
+
location = f"{issue.statement_sid} (#{statement_num})"
|
|
379
381
|
if issue.line_number is not None:
|
|
380
382
|
location += f" @L{issue.line_number}"
|
|
381
383
|
|
|
@@ -399,19 +401,24 @@ class EnhancedFormatter(OutputFormatter):
|
|
|
399
401
|
details.append(f"Condition: {issue.condition_key}")
|
|
400
402
|
msg_node.add(Text(" • ".join(details), style="dim cyan"))
|
|
401
403
|
|
|
402
|
-
# Suggestion
|
|
403
|
-
if issue.suggestion:
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
404
|
+
# Suggestion and Example - combine into single node to reduce spacing
|
|
405
|
+
if issue.suggestion or issue.example:
|
|
406
|
+
combined_text = Text()
|
|
407
|
+
|
|
408
|
+
# Add suggestion
|
|
409
|
+
if issue.suggestion:
|
|
410
|
+
combined_text.append("💡 ", style="yellow")
|
|
411
|
+
combined_text.append(issue.suggestion, style="italic yellow")
|
|
412
|
+
|
|
413
|
+
# Add example on same node (reduces vertical spacing)
|
|
414
|
+
if issue.example:
|
|
415
|
+
if issue.suggestion:
|
|
416
|
+
combined_text.append("\n", style="yellow") # Single newline separator
|
|
417
|
+
combined_text.append("Example:", style="bold cyan")
|
|
418
|
+
combined_text.append("\n")
|
|
419
|
+
combined_text.append(issue.example, style="dim")
|
|
408
420
|
|
|
409
|
-
|
|
410
|
-
if issue.example:
|
|
411
|
-
msg_node.add(Text("Example:", style="bold cyan"))
|
|
412
|
-
# Show example code with syntax highlighting
|
|
413
|
-
example_text = Text(issue.example, style="dim")
|
|
414
|
-
msg_node.add(example_text)
|
|
421
|
+
msg_node.add(combined_text)
|
|
415
422
|
|
|
416
423
|
def _print_final_status(self, console: Console, report: ValidationReport) -> None:
|
|
417
424
|
"""Print final status panel."""
|
|
@@ -461,7 +468,7 @@ class EnhancedFormatter(OutputFormatter):
|
|
|
461
468
|
# Combine status and message
|
|
462
469
|
final_text = Text()
|
|
463
470
|
final_text.append(status)
|
|
464
|
-
final_text.append("\n\n
|
|
471
|
+
final_text.append("\n") # Reduced from \n\n to single newline
|
|
465
472
|
final_text.append(message)
|
|
466
473
|
|
|
467
474
|
console.print(
|
iam_validator/core/models.py
CHANGED
|
@@ -233,6 +233,8 @@ class ValidationIssue(BaseModel):
|
|
|
233
233
|
if include_identifier:
|
|
234
234
|
parts.append(f"{constants.REVIEW_IDENTIFIER}\n")
|
|
235
235
|
parts.append(f"{constants.BOT_IDENTIFIER}\n")
|
|
236
|
+
# Add issue type identifier to allow multiple issues at same line
|
|
237
|
+
parts.append(f"<!-- issue-type: {self.issue_type} -->\n")
|
|
236
238
|
|
|
237
239
|
# Build statement context for better navigation
|
|
238
240
|
statement_context = f"Statement[{self.statement_index}]"
|
|
@@ -13,6 +13,7 @@ from iam_validator.core.constants import (
|
|
|
13
13
|
REVIEW_IDENTIFIER,
|
|
14
14
|
SUMMARY_IDENTIFIER,
|
|
15
15
|
)
|
|
16
|
+
from iam_validator.core.diff_parser import DiffParser
|
|
16
17
|
from iam_validator.core.label_manager import LabelManager
|
|
17
18
|
from iam_validator.core.models import ValidationIssue, ValidationReport
|
|
18
19
|
from iam_validator.core.report import ReportGenerator
|
|
@@ -21,6 +22,34 @@ from iam_validator.integrations.github_integration import GitHubIntegration, Rev
|
|
|
21
22
|
logger = logging.getLogger(__name__)
|
|
22
23
|
|
|
23
24
|
|
|
25
|
+
class ContextIssue:
|
|
26
|
+
"""Represents an issue in a modified statement but on an unchanged line.
|
|
27
|
+
|
|
28
|
+
These issues are shown in the summary comment rather than as inline comments,
|
|
29
|
+
since GitHub only allows comments on lines that appear in the PR diff.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
file_path: str,
|
|
35
|
+
statement_index: int,
|
|
36
|
+
line_number: int,
|
|
37
|
+
issue: ValidationIssue,
|
|
38
|
+
):
|
|
39
|
+
"""Initialize context issue.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
file_path: Relative path to the file
|
|
43
|
+
statement_index: Zero-based statement index
|
|
44
|
+
line_number: Line number where the issue exists
|
|
45
|
+
issue: The validation issue
|
|
46
|
+
"""
|
|
47
|
+
self.file_path = file_path
|
|
48
|
+
self.statement_index = statement_index
|
|
49
|
+
self.line_number = line_number
|
|
50
|
+
self.issue = issue
|
|
51
|
+
|
|
52
|
+
|
|
24
53
|
class PRCommenter:
|
|
25
54
|
"""Posts validation findings as PR comments."""
|
|
26
55
|
|
|
@@ -41,6 +70,7 @@ class PRCommenter:
|
|
|
41
70
|
Args:
|
|
42
71
|
github: GitHubIntegration instance (will create one if None)
|
|
43
72
|
cleanup_old_comments: Whether to clean up old bot comments before posting new ones
|
|
73
|
+
(kept for backward compatibility but now handled automatically)
|
|
44
74
|
fail_on_severities: List of severity levels that should trigger REQUEST_CHANGES
|
|
45
75
|
(e.g., ["error", "critical", "high"])
|
|
46
76
|
severity_labels: Mapping of severity levels to label name(s) for automatic label management
|
|
@@ -54,6 +84,8 @@ class PRCommenter:
|
|
|
54
84
|
self.cleanup_old_comments = cleanup_old_comments
|
|
55
85
|
self.fail_on_severities = fail_on_severities or ["error", "critical"]
|
|
56
86
|
self.severity_labels = severity_labels or {}
|
|
87
|
+
# Track issues in modified statements that are on unchanged lines
|
|
88
|
+
self._context_issues: list[ContextIssue] = []
|
|
57
89
|
|
|
58
90
|
async def post_findings_to_pr(
|
|
59
91
|
self,
|
|
@@ -86,10 +118,15 @@ class PRCommenter:
|
|
|
86
118
|
|
|
87
119
|
success = True
|
|
88
120
|
|
|
89
|
-
#
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
121
|
+
# Note: Cleanup is now handled smartly by update_or_create_review_comments()
|
|
122
|
+
# It will update existing comments, create new ones, and delete resolved ones
|
|
123
|
+
|
|
124
|
+
# Post line-specific review comments FIRST
|
|
125
|
+
# (This populates self._context_issues)
|
|
126
|
+
if create_review:
|
|
127
|
+
if not await self._post_review_comments(report):
|
|
128
|
+
logger.error("Failed to post review comments")
|
|
129
|
+
success = False
|
|
93
130
|
|
|
94
131
|
# Post summary comment (potentially as multiple parts)
|
|
95
132
|
if add_summary_comment:
|
|
@@ -108,12 +145,6 @@ class PRCommenter:
|
|
|
108
145
|
else:
|
|
109
146
|
logger.info("Posted summary comment")
|
|
110
147
|
|
|
111
|
-
# Post line-specific review comments
|
|
112
|
-
if create_review:
|
|
113
|
-
if not await self._post_review_comments(report):
|
|
114
|
-
logger.error("Failed to post review comments")
|
|
115
|
-
success = False
|
|
116
|
-
|
|
117
148
|
# Manage PR labels based on severity findings
|
|
118
149
|
if manage_labels and self.severity_labels:
|
|
119
150
|
label_manager = LabelManager(self.github, self.severity_labels)
|
|
@@ -129,7 +160,11 @@ class PRCommenter:
|
|
|
129
160
|
return success
|
|
130
161
|
|
|
131
162
|
async def _post_review_comments(self, report: ValidationReport) -> bool:
|
|
132
|
-
"""Post line-specific review comments.
|
|
163
|
+
"""Post line-specific review comments with strict diff filtering.
|
|
164
|
+
|
|
165
|
+
Only posts comments on lines that were actually changed in the PR.
|
|
166
|
+
Issues in modified statements but on unchanged lines are tracked in
|
|
167
|
+
self._context_issues for inclusion in the summary comment.
|
|
133
168
|
|
|
134
169
|
Args:
|
|
135
170
|
report: Validation report
|
|
@@ -140,8 +175,25 @@ class PRCommenter:
|
|
|
140
175
|
if not self.github:
|
|
141
176
|
return False
|
|
142
177
|
|
|
178
|
+
# Clear context issues from previous runs
|
|
179
|
+
self._context_issues = []
|
|
180
|
+
|
|
181
|
+
# Fetch PR diff information
|
|
182
|
+
logger.info("Fetching PR diff information for strict filtering...")
|
|
183
|
+
pr_files = await self.github.get_pr_files()
|
|
184
|
+
if not pr_files:
|
|
185
|
+
logger.warning(
|
|
186
|
+
"Could not fetch PR diff information. "
|
|
187
|
+
"Falling back to unfiltered commenting (may fail if lines not in diff)."
|
|
188
|
+
)
|
|
189
|
+
parsed_diffs = {}
|
|
190
|
+
else:
|
|
191
|
+
parsed_diffs = DiffParser.parse_pr_files(pr_files)
|
|
192
|
+
logger.info(f"Parsed diffs for {len(parsed_diffs)} file(s)")
|
|
193
|
+
|
|
143
194
|
# Group issues by file
|
|
144
|
-
|
|
195
|
+
inline_comments: list[dict[str, Any]] = []
|
|
196
|
+
context_issue_count = 0
|
|
145
197
|
|
|
146
198
|
for result in report.results:
|
|
147
199
|
if not result.issues:
|
|
@@ -155,75 +207,149 @@ class PRCommenter:
|
|
|
155
207
|
)
|
|
156
208
|
continue
|
|
157
209
|
|
|
158
|
-
#
|
|
210
|
+
# Get diff info for this file
|
|
211
|
+
diff_info = parsed_diffs.get(relative_path)
|
|
212
|
+
if not diff_info:
|
|
213
|
+
logger.debug(
|
|
214
|
+
f"{relative_path} not in PR diff or no changes, skipping inline comments"
|
|
215
|
+
)
|
|
216
|
+
# Still process issues for summary
|
|
217
|
+
for issue in result.issues:
|
|
218
|
+
if issue.statement_index is not None:
|
|
219
|
+
line_num = self._find_issue_line(
|
|
220
|
+
issue, result.policy_file, self._get_line_mapping(result.policy_file)
|
|
221
|
+
)
|
|
222
|
+
if line_num:
|
|
223
|
+
self._context_issues.append(
|
|
224
|
+
ContextIssue(relative_path, issue.statement_index, line_num, issue)
|
|
225
|
+
)
|
|
226
|
+
context_issue_count += 1
|
|
227
|
+
continue
|
|
228
|
+
|
|
229
|
+
# Get line mapping and modified statements for this file
|
|
159
230
|
line_mapping = self._get_line_mapping(result.policy_file)
|
|
231
|
+
modified_statements = DiffParser.get_modified_statements(
|
|
232
|
+
line_mapping, diff_info.changed_lines, result.policy_file
|
|
233
|
+
)
|
|
160
234
|
|
|
235
|
+
logger.debug(
|
|
236
|
+
f"{relative_path}: {len(diff_info.changed_lines)} changed lines, "
|
|
237
|
+
f"{len(modified_statements)} modified statements"
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
# Process each issue with strict filtering
|
|
161
241
|
for issue in result.issues:
|
|
162
|
-
# Determine the line number for this issue
|
|
163
242
|
line_number = self._find_issue_line(issue, result.policy_file, line_mapping)
|
|
164
243
|
|
|
165
|
-
if line_number:
|
|
166
|
-
|
|
167
|
-
"
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
244
|
+
if not line_number:
|
|
245
|
+
logger.debug(
|
|
246
|
+
f"Could not determine line number for issue in {relative_path}: {issue.issue_type}"
|
|
247
|
+
)
|
|
248
|
+
continue
|
|
171
249
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
250
|
+
# SPECIAL CASE: Policy-level issues (privilege escalation, etc.)
|
|
251
|
+
# Post to first available line in diff, preferring line 1 if available
|
|
252
|
+
if issue.statement_index == -1:
|
|
253
|
+
# Try to find the best line to post the comment
|
|
254
|
+
comment_line = None
|
|
255
|
+
|
|
256
|
+
if line_number in diff_info.changed_lines:
|
|
257
|
+
# Best case: line 1 is in the diff
|
|
258
|
+
comment_line = line_number
|
|
259
|
+
elif diff_info.changed_lines:
|
|
260
|
+
# Fallback: use the first changed line in the file
|
|
261
|
+
# This ensures policy-level issues always appear as inline comments
|
|
262
|
+
comment_line = min(diff_info.changed_lines)
|
|
263
|
+
logger.debug(
|
|
264
|
+
f"Policy-level issue at line {line_number}, posting to first changed line {comment_line}"
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
if comment_line:
|
|
268
|
+
# Post as inline comment at the determined line
|
|
269
|
+
inline_comments.append(
|
|
270
|
+
{
|
|
271
|
+
"path": relative_path,
|
|
272
|
+
"line": comment_line,
|
|
273
|
+
"body": issue.to_pr_comment(),
|
|
274
|
+
}
|
|
275
|
+
)
|
|
276
|
+
logger.debug(
|
|
277
|
+
f"Policy-level inline comment: {relative_path}:{comment_line} - {issue.issue_type}"
|
|
278
|
+
)
|
|
279
|
+
else:
|
|
280
|
+
# No changed lines in file - add to summary comment
|
|
281
|
+
self._context_issues.append(
|
|
282
|
+
ContextIssue(relative_path, issue.statement_index, line_number, issue)
|
|
283
|
+
)
|
|
284
|
+
context_issue_count += 1
|
|
285
|
+
logger.debug(
|
|
286
|
+
f"Policy-level issue (no diff lines): {relative_path} - {issue.issue_type}"
|
|
287
|
+
)
|
|
288
|
+
# STRICT FILTERING: Only comment if line is in the diff
|
|
289
|
+
elif line_number in diff_info.changed_lines:
|
|
290
|
+
# Exact match - post inline comment
|
|
291
|
+
inline_comments.append(
|
|
292
|
+
{
|
|
293
|
+
"path": relative_path,
|
|
294
|
+
"line": line_number,
|
|
295
|
+
"body": issue.to_pr_comment(),
|
|
296
|
+
}
|
|
297
|
+
)
|
|
298
|
+
logger.debug(
|
|
299
|
+
f"Inline comment: {relative_path}:{line_number} - {issue.issue_type}"
|
|
300
|
+
)
|
|
301
|
+
elif issue.statement_index in modified_statements:
|
|
302
|
+
# Issue in modified statement but on unchanged line - save for summary
|
|
303
|
+
self._context_issues.append(
|
|
304
|
+
ContextIssue(relative_path, issue.statement_index, line_number, issue)
|
|
305
|
+
)
|
|
306
|
+
context_issue_count += 1
|
|
175
307
|
logger.debug(
|
|
176
|
-
f"
|
|
308
|
+
f"Context issue: {relative_path}:{line_number} (statement {issue.statement_index} modified) - {issue.issue_type}"
|
|
177
309
|
)
|
|
178
310
|
else:
|
|
311
|
+
# Issue in completely unchanged statement - ignore for inline and summary
|
|
179
312
|
logger.debug(
|
|
180
|
-
f"
|
|
313
|
+
f"Skipped issue in unchanged statement: {relative_path}:{line_number} - {issue.issue_type}"
|
|
181
314
|
)
|
|
182
315
|
|
|
183
|
-
#
|
|
184
|
-
if not comments_by_file:
|
|
185
|
-
logger.info("No line-specific comments to post")
|
|
186
|
-
return True
|
|
187
|
-
|
|
188
|
-
# Flatten comments list
|
|
189
|
-
all_comments = []
|
|
190
|
-
for file_comments in comments_by_file.values():
|
|
191
|
-
all_comments.extend(file_comments)
|
|
192
|
-
|
|
316
|
+
# Log filtering results
|
|
193
317
|
logger.info(
|
|
194
|
-
f"
|
|
318
|
+
f"Diff filtering results: {len(inline_comments)} inline comments, "
|
|
319
|
+
f"{context_issue_count} context issues for summary"
|
|
195
320
|
)
|
|
196
321
|
|
|
197
|
-
#
|
|
198
|
-
|
|
199
|
-
logger.
|
|
322
|
+
# If no inline comments, skip review creation but still return success
|
|
323
|
+
if not inline_comments:
|
|
324
|
+
logger.info("No inline comments to post (after diff filtering)")
|
|
325
|
+
return True
|
|
200
326
|
|
|
201
327
|
# Determine review event based on fail_on_severities config
|
|
202
|
-
# Check if any issue has a severity that should trigger REQUEST_CHANGES
|
|
203
328
|
has_blocking_issues = any(
|
|
204
329
|
issue.severity in self.fail_on_severities
|
|
205
330
|
for result in report.results
|
|
206
331
|
for issue in result.issues
|
|
207
332
|
)
|
|
208
333
|
|
|
209
|
-
# Set review event: request changes if any blocking issues, else comment
|
|
210
334
|
event = ReviewEvent.REQUEST_CHANGES if has_blocking_issues else ReviewEvent.COMMENT
|
|
211
|
-
logger.info(
|
|
335
|
+
logger.info(
|
|
336
|
+
f"Creating PR review with {len(inline_comments)} comments, event: {event.value}"
|
|
337
|
+
)
|
|
212
338
|
|
|
213
|
-
# Post review with
|
|
214
|
-
# Only include the identifier for cleanup purposes
|
|
339
|
+
# Post review with smart update-or-create logic
|
|
215
340
|
review_body = f"{self.REVIEW_IDENTIFIER}"
|
|
216
341
|
|
|
217
|
-
success = await self.github.
|
|
218
|
-
comments=
|
|
342
|
+
success = await self.github.update_or_create_review_comments(
|
|
343
|
+
comments=inline_comments,
|
|
219
344
|
body=review_body,
|
|
220
345
|
event=event,
|
|
346
|
+
identifier=self.REVIEW_IDENTIFIER,
|
|
221
347
|
)
|
|
222
348
|
|
|
223
349
|
if success:
|
|
224
|
-
logger.info(
|
|
350
|
+
logger.info("Successfully managed PR review comments (update/create/delete)")
|
|
225
351
|
else:
|
|
226
|
-
logger.error("Failed to
|
|
352
|
+
logger.error("Failed to manage PR review comments")
|
|
227
353
|
|
|
228
354
|
return success
|
|
229
355
|
|
|
@@ -238,8 +364,8 @@ class PRCommenter:
|
|
|
238
364
|
Returns:
|
|
239
365
|
Relative path from repository root, or None if cannot be determined
|
|
240
366
|
"""
|
|
241
|
-
import os
|
|
242
|
-
from pathlib import Path
|
|
367
|
+
import os # pylint: disable=import-outside-toplevel
|
|
368
|
+
from pathlib import Path # pylint: disable=import-outside-toplevel
|
|
243
369
|
|
|
244
370
|
# If already relative, use as-is
|
|
245
371
|
if not os.path.isabs(policy_file):
|
|
@@ -421,7 +547,9 @@ async def post_report_to_pr(
|
|
|
421
547
|
report = ValidationReport.model_validate(report_data)
|
|
422
548
|
|
|
423
549
|
# Load config to get fail_on_severity and severity_labels settings
|
|
424
|
-
from iam_validator.core.config.config_loader import
|
|
550
|
+
from iam_validator.core.config.config_loader import ( # pylint: disable=import-outside-toplevel
|
|
551
|
+
ConfigLoader,
|
|
552
|
+
)
|
|
425
553
|
|
|
426
554
|
config = ConfigLoader.load_config(config_path)
|
|
427
555
|
fail_on_severities = config.get_setting("fail_on_severity", ["error", "critical"])
|
iam_validator/core/report.py
CHANGED
|
@@ -819,24 +819,26 @@ class ReportGenerator:
|
|
|
819
819
|
issue: The validation issue to format
|
|
820
820
|
policy_file: Optional policy file path (currently unused, kept for compatibility)
|
|
821
821
|
"""
|
|
822
|
-
#
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
# Build statement location reference
|
|
826
|
-
# Note: We show plain text here instead of links because:
|
|
827
|
-
# 1. GitHub's diff anchor format only works for files in the PR diff
|
|
828
|
-
# 2. Inline review comments (posted separately) already provide perfect navigation
|
|
829
|
-
# 3. Summary comment is for overview, not detailed navigation
|
|
830
|
-
if issue.line_number:
|
|
831
|
-
location = f"Statement {statement_num} (Line {issue.line_number})"
|
|
832
|
-
if issue.statement_sid:
|
|
833
|
-
location = (
|
|
834
|
-
f"`{issue.statement_sid}` (statement {statement_num}, line {issue.line_number})"
|
|
835
|
-
)
|
|
822
|
+
# Handle policy-level issues (statement_index = -1)
|
|
823
|
+
if issue.statement_index == -1:
|
|
824
|
+
location = "Policy-level"
|
|
836
825
|
else:
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
826
|
+
# Use 1-indexed statement numbers for user-facing output
|
|
827
|
+
statement_num = issue.statement_index + 1
|
|
828
|
+
|
|
829
|
+
# Build statement location reference
|
|
830
|
+
# Note: We show plain text here instead of links because:
|
|
831
|
+
# 1. GitHub's diff anchor format only works for files in the PR diff
|
|
832
|
+
# 2. Inline review comments (posted separately) already provide perfect navigation
|
|
833
|
+
# 3. Summary comment is for overview, not detailed navigation
|
|
834
|
+
if issue.line_number:
|
|
835
|
+
location = f"Statement {statement_num} (Line {issue.line_number})"
|
|
836
|
+
if issue.statement_sid:
|
|
837
|
+
location = f"`{issue.statement_sid}` (statement {statement_num}, line {issue.line_number})"
|
|
838
|
+
else:
|
|
839
|
+
location = f"Statement {statement_num}"
|
|
840
|
+
if issue.statement_sid:
|
|
841
|
+
location = f"`{issue.statement_sid}` (statement {statement_num})"
|
|
840
842
|
|
|
841
843
|
parts = []
|
|
842
844
|
|