iam-policy-validator 1.14.3__py3-none-any.whl → 1.14.5__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.14.3.dist-info → iam_policy_validator-1.14.5.dist-info}/METADATA +1 -1
- {iam_policy_validator-1.14.3.dist-info → iam_policy_validator-1.14.5.dist-info}/RECORD +10 -10
- iam_validator/__version__.py +1 -1
- iam_validator/core/label_manager.py +30 -8
- iam_validator/core/pr_commenter.py +69 -4
- iam_validator/core/report.py +117 -7
- iam_validator/integrations/github_integration.py +20 -0
- {iam_policy_validator-1.14.3.dist-info → iam_policy_validator-1.14.5.dist-info}/WHEEL +0 -0
- {iam_policy_validator-1.14.3.dist-info → iam_policy_validator-1.14.5.dist-info}/entry_points.txt +0 -0
- {iam_policy_validator-1.14.3.dist-info → iam_policy_validator-1.14.5.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: iam-policy-validator
|
|
3
|
-
Version: 1.14.
|
|
3
|
+
Version: 1.14.5
|
|
4
4
|
Summary: Validate AWS IAM policies for correctness and security using AWS Service Reference API
|
|
5
5
|
Project-URL: Homepage, https://github.com/boogy/iam-policy-validator
|
|
6
6
|
Project-URL: Documentation, https://github.com/boogy/iam-policy-validator/tree/main/docs
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
iam_validator/__init__.py,sha256=xHdUASOxFHwEXfT_GSr_KrkLlnxZ-pAAr1wW1PwAGko,693
|
|
2
2
|
iam_validator/__main__.py,sha256=to_nz3n_IerJpVVZZ6WSFlFR5s_06J0csfPOTfQZG8g,197
|
|
3
|
-
iam_validator/__version__.py,sha256=
|
|
3
|
+
iam_validator/__version__.py,sha256=8ouM89pP7JLVFY6dwrTsOuZeWcu_xuQ3YwT7-1g9xn8,374
|
|
4
4
|
iam_validator/checks/__init__.py,sha256=OTkPnmlelu4YjMO8krjhu2wXiTV72RzopA5u1SfPQA0,1990
|
|
5
5
|
iam_validator/checks/action_condition_enforcement.py,sha256=2-XUMbof9tQ7SHZNmAHMkR1DgbOIzY2eFWlp9S9dwLk,60625
|
|
6
6
|
iam_validator/checks/action_resource_matching.py,sha256=qND0hfDgNoxFEdLWwrxOPVDfdj3k50nzedT2qF7nK7o,19428
|
|
@@ -49,12 +49,12 @@ iam_validator/core/finding_fingerprint.py,sha256=NJIlu8NhdenWbLS7ww8LyWFasJgpKWN
|
|
|
49
49
|
iam_validator/core/ignore_patterns.py,sha256=pZqDJBtkbck-85QK5eFPM5ZOPEKs3McRh3avqiCT5z0,10398
|
|
50
50
|
iam_validator/core/ignore_processor.py,sha256=zgWfS-4BU4c_W6VxUxHIHorMtB5XzB410wZ3bbzVgH8,10686
|
|
51
51
|
iam_validator/core/ignored_findings.py,sha256=b4PySz46so1rGKNt4prg2dkysHPfTJP4wsHYorVn1FA,12756
|
|
52
|
-
iam_validator/core/label_manager.py,sha256=
|
|
52
|
+
iam_validator/core/label_manager.py,sha256=qKQ60shsW8yJELkHgd9rXgzLW9oKErPd4hFTTQkHjbI,8776
|
|
53
53
|
iam_validator/core/models.py,sha256=lXUadIsTpp_j0Vt89Ez7aJkTKs2GD2ty3Ukl2NeY9Zo,15680
|
|
54
54
|
iam_validator/core/policy_checks.py,sha256=FNVuS2GTffwCjjrlupVIazC172gSxKYAAT_ObV6Apbo,8803
|
|
55
55
|
iam_validator/core/policy_loader.py,sha256=iid3mGfDzSXASzKDqbLnrqJHBdVQvvebofVqNImsGKM,29201
|
|
56
|
-
iam_validator/core/pr_commenter.py,sha256=
|
|
57
|
-
iam_validator/core/report.py,sha256=
|
|
56
|
+
iam_validator/core/pr_commenter.py,sha256=IZu2FQqzw73U_8ugTUq197ECLqk9mRCQpTWXPu5qk0k,35490
|
|
57
|
+
iam_validator/core/report.py,sha256=IEHjNe6v_9nvcGA8_FNbXdG0AoV-yHVjiP1KQKnpEys,41376
|
|
58
58
|
iam_validator/core/aws_service/__init__.py,sha256=UqMh4HUdGlx2QF5OoueJJ2UlCnhX4QW_x3KeE_bxRQc,735
|
|
59
59
|
iam_validator/core/aws_service/cache.py,sha256=DPuOOPPJC867KAYgV1e0RyQs_k3mtefMdYli3jPaN64,3589
|
|
60
60
|
iam_validator/core/aws_service/client.py,sha256=Zv7rIpEFdUCDXKGp3migPDkj8L5eZltgrGe64M2t2Ko,7336
|
|
@@ -85,7 +85,7 @@ iam_validator/core/formatters/json.py,sha256=A7gZ8P32GEdbDvrSn6v56yQ4fOP_kyMaoFV
|
|
|
85
85
|
iam_validator/core/formatters/markdown.py,sha256=dk4STeY-tOEZsVrlmolIEqZvWYP9JhRtygxxNA49DEE,2293
|
|
86
86
|
iam_validator/core/formatters/sarif.py,sha256=03MHSyuZm9FlzaPeWg7wH-UTzzCDhSy6vMPrFpFNkS8,18884
|
|
87
87
|
iam_validator/integrations/__init__.py,sha256=7Hlor_X9j0NZaEjFuSvoXAAuSKQ-zgY19Rk-Dz3JpKo,616
|
|
88
|
-
iam_validator/integrations/github_integration.py,sha256=
|
|
88
|
+
iam_validator/integrations/github_integration.py,sha256=IKhJW_v_lGZiuyPN_xWULzv2YBbaXHn8zBfaOdUm28g,69054
|
|
89
89
|
iam_validator/integrations/ms_teams.py,sha256=t2PlWuTDb6GGH-eDU1jnOKd8D1w4FCB68bahGA7MJcE,14475
|
|
90
90
|
iam_validator/sdk/__init__.py,sha256=AZLnfdn3A9AWb0pMhsbu3GAOAzt6rV7Fi3E3d9_3ZdI,6388
|
|
91
91
|
iam_validator/sdk/arn_matching.py,sha256=HSDpLltOYISq-SoPebAlM89mKOaUaghq_04urchEFDA,12778
|
|
@@ -99,8 +99,8 @@ iam_validator/utils/__init__.py,sha256=NveA2F3G1E6-ANZzFr7J6Q6u5mogvMp862iFokmYu
|
|
|
99
99
|
iam_validator/utils/cache.py,sha256=wOQKOBeoG6QqC5f0oXcHz63Cjtu_-SsSS-0pTSwyAiM,3254
|
|
100
100
|
iam_validator/utils/regex.py,sha256=xHoMECttb7qaMhts-c9b0GIxdhHNZTt-UBr7wNhWfzg,6219
|
|
101
101
|
iam_validator/utils/terminal.py,sha256=FsRaRMH_JAyDgXWBCOgOEhbS89cs17HCmKYoughq5io,724
|
|
102
|
-
iam_policy_validator-1.14.
|
|
103
|
-
iam_policy_validator-1.14.
|
|
104
|
-
iam_policy_validator-1.14.
|
|
105
|
-
iam_policy_validator-1.14.
|
|
106
|
-
iam_policy_validator-1.14.
|
|
102
|
+
iam_policy_validator-1.14.5.dist-info/METADATA,sha256=h6M6__GqJW5fWPtV0cEDqZ4sK259K5ulz68Jgt6COQE,34456
|
|
103
|
+
iam_policy_validator-1.14.5.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
104
|
+
iam_policy_validator-1.14.5.dist-info/entry_points.txt,sha256=8HtWd8O7mvPiPdZR5YbzY8or_qcqLM4-pKaFdhtFT8M,62
|
|
105
|
+
iam_policy_validator-1.14.5.dist-info/licenses/LICENSE,sha256=AMnbFTBDcK4_MITe2wiQBkj0vg-jjBBhsc43ydC7tt4,1098
|
|
106
|
+
iam_policy_validator-1.14.5.dist-info/RECORD,,
|
iam_validator/__version__.py
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
This file is the single source of truth for the package version.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
__version__ = "1.14.
|
|
6
|
+
__version__ = "1.14.5"
|
|
7
7
|
# Parse version, handling pre-release suffixes like -rc, -alpha, -beta
|
|
8
8
|
_version_base = __version__.split("-", maxsplit=1)[0] # Remove pre-release suffix if present
|
|
9
9
|
__version_info__ = tuple(int(part) for part in _version_base.split("."))
|
|
@@ -6,10 +6,11 @@ When those severities are not found, it removes the labels if present.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import logging
|
|
9
|
+
from collections.abc import Callable
|
|
9
10
|
from typing import TYPE_CHECKING
|
|
10
11
|
|
|
11
12
|
if TYPE_CHECKING:
|
|
12
|
-
from iam_validator.core.models import PolicyValidationResult, ValidationReport
|
|
13
|
+
from iam_validator.core.models import PolicyValidationResult, ValidationIssue, ValidationReport
|
|
13
14
|
from iam_validator.integrations.github_integration import GitHubIntegration
|
|
14
15
|
|
|
15
16
|
logger = logging.getLogger(__name__)
|
|
@@ -48,11 +49,17 @@ class LabelManager:
|
|
|
48
49
|
"""
|
|
49
50
|
return bool(self.severity_labels) and self.github.is_configured()
|
|
50
51
|
|
|
51
|
-
def _get_severities_in_results(
|
|
52
|
+
def _get_severities_in_results(
|
|
53
|
+
self,
|
|
54
|
+
results: list["PolicyValidationResult"],
|
|
55
|
+
is_issue_ignored: Callable[["ValidationIssue", str], bool] | None = None,
|
|
56
|
+
) -> set[str]:
|
|
52
57
|
"""Extract all severity levels found in validation results.
|
|
53
58
|
|
|
54
59
|
Args:
|
|
55
60
|
results: List of PolicyValidationResult objects
|
|
61
|
+
is_issue_ignored: Optional callback to check if an issue is ignored.
|
|
62
|
+
Takes (issue, file_path) and returns True if ignored.
|
|
56
63
|
|
|
57
64
|
Returns:
|
|
58
65
|
Set of severity levels found (e.g., {"error", "critical", "high"})
|
|
@@ -60,6 +67,9 @@ class LabelManager:
|
|
|
60
67
|
severities = set()
|
|
61
68
|
for result in results:
|
|
62
69
|
for issue in result.issues:
|
|
70
|
+
# Skip ignored issues if a filter is provided
|
|
71
|
+
if is_issue_ignored and is_issue_ignored(issue, result.policy_file):
|
|
72
|
+
continue
|
|
63
73
|
severities.add(issue.severity)
|
|
64
74
|
return severities
|
|
65
75
|
|
|
@@ -113,17 +123,22 @@ class LabelManager:
|
|
|
113
123
|
return labels_to_remove
|
|
114
124
|
|
|
115
125
|
async def manage_labels_from_results(
|
|
116
|
-
self,
|
|
126
|
+
self,
|
|
127
|
+
results: list["PolicyValidationResult"],
|
|
128
|
+
is_issue_ignored: Callable[["ValidationIssue", str], bool] | None = None,
|
|
117
129
|
) -> tuple[bool, int, int]:
|
|
118
130
|
"""Manage PR labels based on validation results.
|
|
119
131
|
|
|
120
132
|
This method will:
|
|
121
|
-
1. Determine which severity levels are present in the results
|
|
133
|
+
1. Determine which severity levels are present in the results (excluding ignored issues)
|
|
122
134
|
2. Add labels for severities that are found
|
|
123
135
|
3. Remove labels for severities that are not found
|
|
124
136
|
|
|
125
137
|
Args:
|
|
126
138
|
results: List of PolicyValidationResult objects
|
|
139
|
+
is_issue_ignored: Optional callback to check if an issue is ignored.
|
|
140
|
+
Takes (issue, file_path) and returns True if ignored.
|
|
141
|
+
Ignored issues are excluded from label determination.
|
|
127
142
|
|
|
128
143
|
Returns:
|
|
129
144
|
Tuple of (success, labels_added, labels_removed)
|
|
@@ -132,8 +147,8 @@ class LabelManager:
|
|
|
132
147
|
logger.debug("Label management not enabled (no severity_labels configured)")
|
|
133
148
|
return (True, 0, 0)
|
|
134
149
|
|
|
135
|
-
# Get all severities found in results
|
|
136
|
-
found_severities = self._get_severities_in_results(results)
|
|
150
|
+
# Get all severities found in results (excluding ignored issues)
|
|
151
|
+
found_severities = self._get_severities_in_results(results, is_issue_ignored)
|
|
137
152
|
logger.debug(f"Found severities in results: {found_severities}")
|
|
138
153
|
|
|
139
154
|
# Determine which labels to apply/remove
|
|
@@ -182,7 +197,11 @@ class LabelManager:
|
|
|
182
197
|
|
|
183
198
|
return (success, added_count, removed_count)
|
|
184
199
|
|
|
185
|
-
async def manage_labels_from_report(
|
|
200
|
+
async def manage_labels_from_report(
|
|
201
|
+
self,
|
|
202
|
+
report: "ValidationReport",
|
|
203
|
+
is_issue_ignored: Callable[["ValidationIssue", str], bool] | None = None,
|
|
204
|
+
) -> tuple[bool, int, int]:
|
|
186
205
|
"""Manage PR labels based on validation report.
|
|
187
206
|
|
|
188
207
|
This is a convenience method that extracts results from the report
|
|
@@ -190,8 +209,11 @@ class LabelManager:
|
|
|
190
209
|
|
|
191
210
|
Args:
|
|
192
211
|
report: ValidationReport object
|
|
212
|
+
is_issue_ignored: Optional callback to check if an issue is ignored.
|
|
213
|
+
Takes (issue, file_path) and returns True if ignored.
|
|
214
|
+
Ignored issues are excluded from label determination.
|
|
193
215
|
|
|
194
216
|
Returns:
|
|
195
217
|
Tuple of (success, labels_added, labels_removed)
|
|
196
218
|
"""
|
|
197
|
-
return await self.manage_labels_from_results(report.results)
|
|
219
|
+
return await self.manage_labels_from_results(report.results, is_issue_ignored)
|
|
@@ -17,7 +17,7 @@ from iam_validator.core.diff_parser import DiffParser
|
|
|
17
17
|
from iam_validator.core.label_manager import LabelManager
|
|
18
18
|
from iam_validator.core.models import ValidationIssue, ValidationReport
|
|
19
19
|
from iam_validator.core.policy_loader import PolicyLineMap, PolicyLoader
|
|
20
|
-
from iam_validator.core.report import ReportGenerator
|
|
20
|
+
from iam_validator.core.report import IgnoredFindingInfo, ReportGenerator
|
|
21
21
|
from iam_validator.integrations.github_integration import GitHubIntegration, ReviewEvent
|
|
22
22
|
|
|
23
23
|
logger = logging.getLogger(__name__)
|
|
@@ -96,6 +96,8 @@ class PRCommenter:
|
|
|
96
96
|
self._context_issues: list[ContextIssue] = []
|
|
97
97
|
# Track ignored finding IDs for the current run
|
|
98
98
|
self._ignored_finding_ids: frozenset[str] = frozenset()
|
|
99
|
+
# Store full ignored findings for display in summary
|
|
100
|
+
self._ignored_findings: dict[str, Any] = {}
|
|
99
101
|
# Cache for PolicyLineMap per file (for field-level line detection)
|
|
100
102
|
self._policy_line_maps: dict[str, PolicyLineMap] = {}
|
|
101
103
|
|
|
@@ -155,8 +157,28 @@ class PRCommenter:
|
|
|
155
157
|
generator = ReportGenerator()
|
|
156
158
|
# Pass ignored count to show in summary
|
|
157
159
|
ignored_count = len(self._ignored_finding_ids) if self._ignored_finding_ids else 0
|
|
160
|
+
|
|
161
|
+
# Convert ignored findings to IgnoredFindingInfo for display
|
|
162
|
+
ignored_findings_info: list[IgnoredFindingInfo] = []
|
|
163
|
+
if self._ignored_findings:
|
|
164
|
+
for finding in self._ignored_findings.values():
|
|
165
|
+
ignored_findings_info.append(
|
|
166
|
+
IgnoredFindingInfo(
|
|
167
|
+
file_path=finding.file_path,
|
|
168
|
+
issue_type=finding.issue_type,
|
|
169
|
+
ignored_by=finding.ignored_by,
|
|
170
|
+
reason=finding.reason,
|
|
171
|
+
)
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
# Determine if all blocking issues are ignored
|
|
175
|
+
all_blocking_ignored = self._are_all_blocking_issues_ignored(report)
|
|
176
|
+
|
|
158
177
|
comment_parts = generator.generate_github_comment_parts(
|
|
159
|
-
report,
|
|
178
|
+
report,
|
|
179
|
+
ignored_count=ignored_count,
|
|
180
|
+
ignored_findings=ignored_findings_info if ignored_findings_info else None,
|
|
181
|
+
all_blocking_ignored=all_blocking_ignored,
|
|
160
182
|
)
|
|
161
183
|
|
|
162
184
|
# Post all parts using the multipart method
|
|
@@ -174,7 +196,17 @@ class PRCommenter:
|
|
|
174
196
|
# Manage PR labels based on severity findings
|
|
175
197
|
if manage_labels and self.severity_labels:
|
|
176
198
|
label_manager = LabelManager(self.github, self.severity_labels)
|
|
177
|
-
|
|
199
|
+
|
|
200
|
+
# Create a filter function that uses relative paths for ignored finding lookup
|
|
201
|
+
def is_issue_ignored_for_labels(issue: ValidationIssue, file_path: str) -> bool:
|
|
202
|
+
relative_path = self._make_relative_path(file_path)
|
|
203
|
+
if not relative_path:
|
|
204
|
+
return False
|
|
205
|
+
return self._is_issue_ignored(issue, relative_path)
|
|
206
|
+
|
|
207
|
+
label_success, added, removed = await label_manager.manage_labels_from_report(
|
|
208
|
+
report, is_issue_ignored=is_issue_ignored_for_labels
|
|
209
|
+
)
|
|
178
210
|
|
|
179
211
|
if not label_success:
|
|
180
212
|
logger.error("Failed to manage PR labels")
|
|
@@ -694,7 +726,10 @@ class PRCommenter:
|
|
|
694
726
|
)
|
|
695
727
|
|
|
696
728
|
store = IgnoredFindingsStore(self.github)
|
|
697
|
-
|
|
729
|
+
# Load full ignored findings for display in summary
|
|
730
|
+
self._ignored_findings = await store.load()
|
|
731
|
+
# Also get just the IDs for fast lookup
|
|
732
|
+
self._ignored_finding_ids = frozenset(self._ignored_findings.keys())
|
|
698
733
|
if self._ignored_finding_ids:
|
|
699
734
|
logger.debug(f"Loaded {len(self._ignored_finding_ids)} ignored finding(s)")
|
|
700
735
|
|
|
@@ -718,6 +753,36 @@ class PRCommenter:
|
|
|
718
753
|
fingerprint = FindingFingerprint.from_issue(issue, file_path)
|
|
719
754
|
return fingerprint.to_hash() in self._ignored_finding_ids
|
|
720
755
|
|
|
756
|
+
def _are_all_blocking_issues_ignored(self, report: ValidationReport) -> bool:
|
|
757
|
+
"""Check if all blocking issues (based on fail_on_severities) are ignored.
|
|
758
|
+
|
|
759
|
+
Args:
|
|
760
|
+
report: The validation report
|
|
761
|
+
|
|
762
|
+
Returns:
|
|
763
|
+
True if there are no unignored blocking issues (i.e., all blocking
|
|
764
|
+
issues have been ignored, or there were no blocking issues to begin with)
|
|
765
|
+
"""
|
|
766
|
+
if not self._ignored_finding_ids:
|
|
767
|
+
# No ignored findings - check if there are any blocking issues at all
|
|
768
|
+
for result in report.results:
|
|
769
|
+
for issue in result.issues:
|
|
770
|
+
if issue.severity in self.fail_on_severities:
|
|
771
|
+
return False
|
|
772
|
+
return True
|
|
773
|
+
|
|
774
|
+
# Check each blocking issue to see if it's ignored
|
|
775
|
+
for result in report.results:
|
|
776
|
+
relative_path = self._make_relative_path(result.policy_file)
|
|
777
|
+
if not relative_path:
|
|
778
|
+
continue
|
|
779
|
+
for issue in result.issues:
|
|
780
|
+
if issue.severity in self.fail_on_severities:
|
|
781
|
+
if not self._is_issue_ignored(issue, relative_path):
|
|
782
|
+
return False
|
|
783
|
+
|
|
784
|
+
return True
|
|
785
|
+
|
|
721
786
|
|
|
722
787
|
async def post_report_to_pr(
|
|
723
788
|
report_file: str,
|
iam_validator/core/report.py
CHANGED
|
@@ -5,6 +5,7 @@ including console output, JSON, and GitHub-flavored markdown for PR comments.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import logging
|
|
8
|
+
from dataclasses import dataclass
|
|
8
9
|
|
|
9
10
|
from rich.console import Console
|
|
10
11
|
from rich.panel import Panel
|
|
@@ -29,6 +30,24 @@ from iam_validator.core.models import (
|
|
|
29
30
|
ValidationReport,
|
|
30
31
|
)
|
|
31
32
|
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class IgnoredFindingInfo:
|
|
36
|
+
"""Information about an ignored finding for display in summary.
|
|
37
|
+
|
|
38
|
+
Attributes:
|
|
39
|
+
file_path: Path to the policy file
|
|
40
|
+
issue_type: Type of issue (e.g., "invalid_action")
|
|
41
|
+
ignored_by: Username who ignored the finding
|
|
42
|
+
reason: Optional reason provided by the user
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
file_path: str
|
|
46
|
+
issue_type: str
|
|
47
|
+
ignored_by: str
|
|
48
|
+
reason: str | None = None
|
|
49
|
+
|
|
50
|
+
|
|
32
51
|
logger = logging.getLogger(__name__)
|
|
33
52
|
|
|
34
53
|
|
|
@@ -239,6 +258,8 @@ class ReportGenerator:
|
|
|
239
258
|
report: ValidationReport,
|
|
240
259
|
max_length_per_part: int = constants.GITHUB_COMMENT_SPLIT_LIMIT,
|
|
241
260
|
ignored_count: int = 0,
|
|
261
|
+
ignored_findings: list[IgnoredFindingInfo] | None = None,
|
|
262
|
+
all_blocking_ignored: bool = False,
|
|
242
263
|
) -> list[str]:
|
|
243
264
|
"""Generate GitHub PR comment(s), splitting into multiple parts if needed.
|
|
244
265
|
|
|
@@ -246,6 +267,8 @@ class ReportGenerator:
|
|
|
246
267
|
report: Validation report
|
|
247
268
|
max_length_per_part: Maximum character length per comment part (default from GITHUB_COMMENT_SPLIT_LIMIT)
|
|
248
269
|
ignored_count: Number of findings that were ignored (will be shown in summary)
|
|
270
|
+
ignored_findings: List of ignored finding details for display in summary
|
|
271
|
+
all_blocking_ignored: True if all blocking issues were ignored (shows "Passed" status)
|
|
249
272
|
|
|
250
273
|
Returns:
|
|
251
274
|
List of comment parts (each under max_length_per_part)
|
|
@@ -257,13 +280,19 @@ class ReportGenerator:
|
|
|
257
280
|
if estimated_size <= max_length_per_part:
|
|
258
281
|
# Try single comment
|
|
259
282
|
single_comment = self.generate_github_comment(
|
|
260
|
-
report,
|
|
283
|
+
report,
|
|
284
|
+
max_length=max_length_per_part * 2,
|
|
285
|
+
ignored_count=ignored_count,
|
|
286
|
+
ignored_findings=ignored_findings,
|
|
287
|
+
all_blocking_ignored=all_blocking_ignored,
|
|
261
288
|
)
|
|
262
289
|
if len(single_comment) <= max_length_per_part:
|
|
263
290
|
return [single_comment]
|
|
264
291
|
|
|
265
292
|
# Need to split into multiple parts
|
|
266
|
-
return self._generate_split_comments(
|
|
293
|
+
return self._generate_split_comments(
|
|
294
|
+
report, max_length_per_part, ignored_count, ignored_findings, all_blocking_ignored
|
|
295
|
+
)
|
|
267
296
|
|
|
268
297
|
def _estimate_report_size(self, report: ValidationReport) -> int:
|
|
269
298
|
"""Estimate the size of the report in characters.
|
|
@@ -280,7 +309,12 @@ class ReportGenerator:
|
|
|
280
309
|
)
|
|
281
310
|
|
|
282
311
|
def _generate_split_comments(
|
|
283
|
-
self,
|
|
312
|
+
self,
|
|
313
|
+
report: ValidationReport,
|
|
314
|
+
max_length: int,
|
|
315
|
+
ignored_count: int = 0,
|
|
316
|
+
ignored_findings: list[IgnoredFindingInfo] | None = None,
|
|
317
|
+
all_blocking_ignored: bool = False,
|
|
284
318
|
) -> list[str]:
|
|
285
319
|
"""Split a large report into multiple comment parts.
|
|
286
320
|
|
|
@@ -288,6 +322,8 @@ class ReportGenerator:
|
|
|
288
322
|
report: Validation report
|
|
289
323
|
max_length: Maximum length per part
|
|
290
324
|
ignored_count: Number of ignored findings to show in summary
|
|
325
|
+
ignored_findings: List of ignored finding details for display
|
|
326
|
+
all_blocking_ignored: True if all blocking issues were ignored
|
|
291
327
|
|
|
292
328
|
Returns:
|
|
293
329
|
List of comment parts
|
|
@@ -295,7 +331,9 @@ class ReportGenerator:
|
|
|
295
331
|
parts: list[str] = []
|
|
296
332
|
|
|
297
333
|
# Generate header (will be in first part only)
|
|
298
|
-
header_lines = self._generate_header(
|
|
334
|
+
header_lines = self._generate_header(
|
|
335
|
+
report, ignored_count, ignored_findings, all_blocking_ignored
|
|
336
|
+
)
|
|
299
337
|
header_content = "\n".join(header_lines)
|
|
300
338
|
|
|
301
339
|
# Generate footer (will be in all parts)
|
|
@@ -388,17 +426,27 @@ class ReportGenerator:
|
|
|
388
426
|
|
|
389
427
|
return parts
|
|
390
428
|
|
|
391
|
-
def _generate_header(
|
|
429
|
+
def _generate_header(
|
|
430
|
+
self,
|
|
431
|
+
report: ValidationReport,
|
|
432
|
+
ignored_count: int = 0,
|
|
433
|
+
ignored_findings: list[IgnoredFindingInfo] | None = None,
|
|
434
|
+
all_blocking_ignored: bool = False,
|
|
435
|
+
) -> list[str]:
|
|
392
436
|
"""Generate the comment header with summary.
|
|
393
437
|
|
|
394
438
|
Args:
|
|
395
439
|
report: Validation report
|
|
396
440
|
ignored_count: Number of findings that were ignored
|
|
441
|
+
ignored_findings: List of ignored finding details for display
|
|
442
|
+
all_blocking_ignored: True if all blocking issues were ignored (shows "Passed" status)
|
|
397
443
|
"""
|
|
398
444
|
lines = []
|
|
399
445
|
|
|
400
446
|
# Title with emoji and status badge
|
|
401
|
-
if
|
|
447
|
+
# Pass if: no invalid policies, OR all blocking issues were ignored
|
|
448
|
+
is_passing = report.invalid_policies == 0 or all_blocking_ignored
|
|
449
|
+
if is_passing:
|
|
402
450
|
lines.append("# 🎉 IAM Policy Validation Passed!")
|
|
403
451
|
status_badge = (
|
|
404
452
|
""
|
|
@@ -456,6 +504,56 @@ class ReportGenerator:
|
|
|
456
504
|
lines.append(f"| 🔵 **Info** | {infos} |")
|
|
457
505
|
lines.append("")
|
|
458
506
|
|
|
507
|
+
# Ignored findings section
|
|
508
|
+
if ignored_findings:
|
|
509
|
+
lines.extend(self._generate_ignored_findings_section(ignored_findings))
|
|
510
|
+
|
|
511
|
+
return lines
|
|
512
|
+
|
|
513
|
+
def _generate_ignored_findings_section(
|
|
514
|
+
self, ignored_findings: list[IgnoredFindingInfo]
|
|
515
|
+
) -> list[str]:
|
|
516
|
+
"""Generate the ignored findings section for the summary comment.
|
|
517
|
+
|
|
518
|
+
Args:
|
|
519
|
+
ignored_findings: List of ignored finding details
|
|
520
|
+
|
|
521
|
+
Returns:
|
|
522
|
+
List of markdown lines for the section
|
|
523
|
+
"""
|
|
524
|
+
lines = []
|
|
525
|
+
lines.append("### 🔕 Ignored Findings")
|
|
526
|
+
lines.append("")
|
|
527
|
+
lines.append(
|
|
528
|
+
"> The following findings were ignored by authorized users and are excluded from validation:"
|
|
529
|
+
)
|
|
530
|
+
lines.append("")
|
|
531
|
+
|
|
532
|
+
lines.append("<details>")
|
|
533
|
+
lines.append(f"<summary>View {len(ignored_findings)} ignored finding(s)</summary>")
|
|
534
|
+
lines.append("")
|
|
535
|
+
|
|
536
|
+
lines.append("| File | Issue Type | Ignored By | Reason |")
|
|
537
|
+
lines.append("|------|------------|------------|--------|")
|
|
538
|
+
|
|
539
|
+
for finding in ignored_findings:
|
|
540
|
+
# Truncate file path if too long
|
|
541
|
+
file_display = finding.file_path
|
|
542
|
+
if len(file_display) > 50:
|
|
543
|
+
file_display = "..." + file_display[-47:]
|
|
544
|
+
|
|
545
|
+
reason_display = finding.reason if finding.reason else "-"
|
|
546
|
+
if len(reason_display) > 30:
|
|
547
|
+
reason_display = reason_display[:27] + "..."
|
|
548
|
+
|
|
549
|
+
lines.append(
|
|
550
|
+
f"| `{file_display}` | `{finding.issue_type}` | @{finding.ignored_by} | {reason_display} |"
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
lines.append("")
|
|
554
|
+
lines.append("</details>")
|
|
555
|
+
lines.append("")
|
|
556
|
+
|
|
459
557
|
return lines
|
|
460
558
|
|
|
461
559
|
def _generate_footer(self) -> str:
|
|
@@ -540,6 +638,8 @@ class ReportGenerator:
|
|
|
540
638
|
report: ValidationReport,
|
|
541
639
|
max_length: int = constants.GITHUB_MAX_COMMENT_LENGTH,
|
|
542
640
|
ignored_count: int = 0,
|
|
641
|
+
ignored_findings: list[IgnoredFindingInfo] | None = None,
|
|
642
|
+
all_blocking_ignored: bool = False,
|
|
543
643
|
) -> str:
|
|
544
644
|
"""Generate a GitHub-flavored markdown comment for PR reviews.
|
|
545
645
|
|
|
@@ -547,6 +647,8 @@ class ReportGenerator:
|
|
|
547
647
|
report: Validation report
|
|
548
648
|
max_length: Maximum character length (default from GITHUB_MAX_COMMENT_LENGTH constant)
|
|
549
649
|
ignored_count: Number of findings that were ignored (will be shown in summary)
|
|
650
|
+
ignored_findings: List of ignored finding details for display in summary
|
|
651
|
+
all_blocking_ignored: True if all blocking issues were ignored (shows "Passed" status)
|
|
550
652
|
|
|
551
653
|
Returns:
|
|
552
654
|
Markdown formatted string
|
|
@@ -554,8 +656,12 @@ class ReportGenerator:
|
|
|
554
656
|
lines = []
|
|
555
657
|
|
|
556
658
|
# Header with emoji and status badge
|
|
659
|
+
# Pass if: no invalid policies, OR all blocking issues were ignored
|
|
557
660
|
has_parsing_errors = len(report.parsing_errors) > 0
|
|
558
|
-
|
|
661
|
+
is_passing = (
|
|
662
|
+
report.invalid_policies == 0 or all_blocking_ignored
|
|
663
|
+
) and not has_parsing_errors
|
|
664
|
+
if is_passing:
|
|
559
665
|
lines.append("# 🎉 IAM Policy Validation Passed!")
|
|
560
666
|
status_badge = (
|
|
561
667
|
""
|
|
@@ -613,6 +719,10 @@ class ReportGenerator:
|
|
|
613
719
|
lines.append(f"| 🔵 **Info** | {infos} |")
|
|
614
720
|
lines.append("")
|
|
615
721
|
|
|
722
|
+
# Ignored findings section
|
|
723
|
+
if ignored_findings:
|
|
724
|
+
lines.extend(self._generate_ignored_findings_section(ignored_findings))
|
|
725
|
+
|
|
616
726
|
# Parsing errors section (if any)
|
|
617
727
|
if report.parsing_errors:
|
|
618
728
|
lines.append("### ⚠️ Parsing Errors")
|
|
@@ -1265,6 +1265,26 @@ class GitHubIntegration:
|
|
|
1265
1265
|
f"{created_count} created, {deleted_count} deleted (resolved)"
|
|
1266
1266
|
)
|
|
1267
1267
|
|
|
1268
|
+
# Step 4: If no new comments were created but we need to submit APPROVE/REQUEST_CHANGES,
|
|
1269
|
+
# submit a review without inline comments to update the PR review state.
|
|
1270
|
+
# This is important when all issues are ignored/resolved - we need to dismiss
|
|
1271
|
+
# the previous REQUEST_CHANGES review by submitting an APPROVE review.
|
|
1272
|
+
if not new_comments_for_review and event in (
|
|
1273
|
+
ReviewEvent.APPROVE,
|
|
1274
|
+
ReviewEvent.REQUEST_CHANGES,
|
|
1275
|
+
):
|
|
1276
|
+
# Only submit if there's a meaningful state change to make
|
|
1277
|
+
# (submitting APPROVE when all issues are resolved/ignored)
|
|
1278
|
+
logger.info(f"Submitting {event.value} review (no inline comments)")
|
|
1279
|
+
success = await self.create_review_with_comments(
|
|
1280
|
+
comments=[],
|
|
1281
|
+
body=body or f"<!-- {identifier} -->\nValidation complete.",
|
|
1282
|
+
event=event,
|
|
1283
|
+
)
|
|
1284
|
+
if not success:
|
|
1285
|
+
logger.warning(f"Failed to submit {event.value} review")
|
|
1286
|
+
# Don't fail the whole operation - comments were managed successfully
|
|
1287
|
+
|
|
1268
1288
|
return True
|
|
1269
1289
|
|
|
1270
1290
|
def _extract_finding_id(self, body: str) -> str | None:
|
|
File without changes
|
{iam_policy_validator-1.14.3.dist-info → iam_policy_validator-1.14.5.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{iam_policy_validator-1.14.3.dist-info → iam_policy_validator-1.14.5.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|