iam-policy-validator 1.9.0__py3-none-any.whl → 1.10.0__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.9.0.dist-info → iam_policy_validator-1.10.0.dist-info}/METADATA +1 -1
- {iam_policy_validator-1.9.0.dist-info → iam_policy_validator-1.10.0.dist-info}/RECORD +10 -9
- iam_validator/__version__.py +1 -1
- iam_validator/commands/validate.py +14 -4
- iam_validator/core/config/defaults.py +10 -0
- iam_validator/core/label_manager.py +197 -0
- iam_validator/core/pr_commenter.py +34 -7
- {iam_policy_validator-1.9.0.dist-info → iam_policy_validator-1.10.0.dist-info}/WHEEL +0 -0
- {iam_policy_validator-1.9.0.dist-info → iam_policy_validator-1.10.0.dist-info}/entry_points.txt +0 -0
- {iam_policy_validator-1.9.0.dist-info → iam_policy_validator-1.10.0.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.
|
|
3
|
+
Version: 1.10.0
|
|
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=FvUNUrZJC1gRVPHFBok-yxk6rVZGHH_JjnMPCje10kg,362
|
|
4
4
|
iam_validator/checks/__init__.py,sha256=OTkPnmlelu4YjMO8krjhu2wXiTV72RzopA5u1SfPQA0,1990
|
|
5
5
|
iam_validator/checks/action_condition_enforcement.py,sha256=0dCH_xX-Xc0uLxtNeRjrpNjWYbdWQRzO1XNcLTSn6sI,51698
|
|
6
6
|
iam_validator/checks/action_resource_matching.py,sha256=WiGJmCIJfx5yituMjZxpKmk-99N6nK20ueN02ddy9oM,19296
|
|
@@ -31,7 +31,7 @@ iam_validator/commands/base.py,sha256=5baCCMwxz7pdQ6XMpWfXFNz7i1l5dB8Qv9dKKR04Gz
|
|
|
31
31
|
iam_validator/commands/cache.py,sha256=llfyQzPE5Azd5YcW0ohYcYjF_OCyiQ1GoJQ982t71lQ,14294
|
|
32
32
|
iam_validator/commands/download_services.py,sha256=KKz3ybMLT8DQUf9aFZ0tilJ-o1b6PE8Pf1pC4K6cT8I,9175
|
|
33
33
|
iam_validator/commands/post_to_pr.py,sha256=CvUXs2xvO-UhluxdfNM6F0TCWD8hDBEOiYw60fm1Dms,2363
|
|
34
|
-
iam_validator/commands/validate.py,sha256=
|
|
34
|
+
iam_validator/commands/validate.py,sha256=cvrgYagYm7W29MYsitZsLcttIIqVKQMRm-bCGY7N3fU,24355
|
|
35
35
|
iam_validator/core/__init__.py,sha256=hYXkSbxplKzhM6dqrVzV4M3k7GKLsZbgExypxKq74gs,376
|
|
36
36
|
iam_validator/core/access_analyzer.py,sha256=mtMaY-FnKjKEVITky_9ywZe1FaCAm61ElRv5Z_ZeC7E,24562
|
|
37
37
|
iam_validator/core/access_analyzer_report.py,sha256=UMm2RNGj2rAKav1zsCw_htQZZRwRC0jjayd2zvKma1A,24896
|
|
@@ -41,10 +41,11 @@ iam_validator/core/cli.py,sha256=PkXiZjlgrQ21QustBbspefYsdbxst4gxoClyG2_HQR8,384
|
|
|
41
41
|
iam_validator/core/condition_validators.py,sha256=7zBjlcf2xGFKGbcFrXSLvWT5tFhWxoqwzhsJqS2E8uY,21524
|
|
42
42
|
iam_validator/core/constants.py,sha256=cVBPgbXr4ALltH_NTSKsgBi6wmndLnOyUWhyBx0ZwrM,6113
|
|
43
43
|
iam_validator/core/ignore_patterns.py,sha256=pZqDJBtkbck-85QK5eFPM5ZOPEKs3McRh3avqiCT5z0,10398
|
|
44
|
+
iam_validator/core/label_manager.py,sha256=48CRASWg98wyjfVF_1pUzj6dm9itzmG7SeIWf0TSUfc,7502
|
|
44
45
|
iam_validator/core/models.py,sha256=f5d9ovtO1xMSwhyBrKIgc2psEq0eugnd3S3ioqurqEE,13242
|
|
45
46
|
iam_validator/core/policy_checks.py,sha256=FNVuS2GTffwCjjrlupVIazC172gSxKYAAT_ObV6Apbo,8803
|
|
46
47
|
iam_validator/core/policy_loader.py,sha256=2KJnXzGg3g9pDXWZHk3DO0xpZnZZ-wXWFEOdQ_naJ8s,17862
|
|
47
|
-
iam_validator/core/pr_commenter.py,sha256=
|
|
48
|
+
iam_validator/core/pr_commenter.py,sha256=NTKoSmjvspYX2rbl3Xn8d611XkTNSfYlGUY0zBHBP4g,16801
|
|
48
49
|
iam_validator/core/report.py,sha256=kzSeWnT1LqWZVA5pqKKz-maVowXVj0djdoShfRhhpz4,35899
|
|
49
50
|
iam_validator/core/aws_service/__init__.py,sha256=UqMh4HUdGlx2QF5OoueJJ2UlCnhX4QW_x3KeE_bxRQc,735
|
|
50
51
|
iam_validator/core/aws_service/cache.py,sha256=DPuOOPPJC867KAYgV1e0RyQs_k3mtefMdYli3jPaN64,3589
|
|
@@ -60,7 +61,7 @@ iam_validator/core/config/aws_global_conditions.py,sha256=gdmMxXGBy95B3uYUG-J7rn
|
|
|
60
61
|
iam_validator/core/config/category_suggestions.py,sha256=QlrYi4BTkxDSTlL7NZGE9BWN-atWetZ6XjkI9F_7YzI,4370
|
|
61
62
|
iam_validator/core/config/condition_requirements.py,sha256=qauIP73HFnOw1dchUeFpg1x7Y7QWkILo3GfxV_dxdQo,7696
|
|
62
63
|
iam_validator/core/config/config_loader.py,sha256=qKD8aR8YAswaFf68pnYJLFNwKznvcc6lNxSQWU3i6SY,17713
|
|
63
|
-
iam_validator/core/config/defaults.py,sha256=
|
|
64
|
+
iam_validator/core/config/defaults.py,sha256=qpFP534dgCQ-vjCdhkK7ZslDoTm9Ftgy20qmYZsSYUI,28637
|
|
64
65
|
iam_validator/core/config/principal_requirements.py,sha256=VCX7fBDgeDTJQyoz7_x7GI7Kf9O1Eu-sbihoHOrKv6o,15105
|
|
65
66
|
iam_validator/core/config/sensitive_actions.py,sha256=uATDIp_TD3OQQlsYTZp79qd1mSK2Bf9hJ0JwcqLBr84,25344
|
|
66
67
|
iam_validator/core/config/service_principals.py,sha256=8pys5H_yycVJ9KTyimAKFYBg83Aol2Iri53wiHjtnEM,3959
|
|
@@ -88,8 +89,8 @@ iam_validator/utils/__init__.py,sha256=NveA2F3G1E6-ANZzFr7J6Q6u5mogvMp862iFokmYu
|
|
|
88
89
|
iam_validator/utils/cache.py,sha256=wOQKOBeoG6QqC5f0oXcHz63Cjtu_-SsSS-0pTSwyAiM,3254
|
|
89
90
|
iam_validator/utils/regex.py,sha256=xHoMECttb7qaMhts-c9b0GIxdhHNZTt-UBr7wNhWfzg,6219
|
|
90
91
|
iam_validator/utils/terminal.py,sha256=FsRaRMH_JAyDgXWBCOgOEhbS89cs17HCmKYoughq5io,724
|
|
91
|
-
iam_policy_validator-1.
|
|
92
|
-
iam_policy_validator-1.
|
|
93
|
-
iam_policy_validator-1.
|
|
94
|
-
iam_policy_validator-1.
|
|
95
|
-
iam_policy_validator-1.
|
|
92
|
+
iam_policy_validator-1.10.0.dist-info/METADATA,sha256=ksZBI5NOZxypD5leqm2BSUOnak43V9EeLBOw1XoLXd0,19070
|
|
93
|
+
iam_policy_validator-1.10.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
94
|
+
iam_policy_validator-1.10.0.dist-info/entry_points.txt,sha256=8HtWd8O7mvPiPdZR5YbzY8or_qcqLM4-pKaFdhtFT8M,62
|
|
95
|
+
iam_policy_validator-1.10.0.dist-info/licenses/LICENSE,sha256=AMnbFTBDcK4_MITe2wiQBkj0vg-jjBBhsc43ydC7tt4,1098
|
|
96
|
+
iam_policy_validator-1.10.0.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.
|
|
6
|
+
__version__ = "1.10.0"
|
|
7
7
|
# Parse version, handling pre-release suffixes like -rc, -alpha, -beta
|
|
8
8
|
_version_base = __version__.split("-")[0] # Remove pre-release suffix if present
|
|
9
9
|
__version_info__ = tuple(int(part) for part in _version_base.split("."))
|
|
@@ -302,12 +302,17 @@ Examples:
|
|
|
302
302
|
from iam_validator.core.config.config_loader import ConfigLoader
|
|
303
303
|
from iam_validator.core.pr_commenter import PRCommenter
|
|
304
304
|
|
|
305
|
-
# Load config to get fail_on_severity
|
|
305
|
+
# Load config to get fail_on_severity and severity_labels settings
|
|
306
306
|
config = ConfigLoader.load_config(config_path)
|
|
307
307
|
fail_on_severities = config.get_setting("fail_on_severity", ["error", "critical"])
|
|
308
|
+
severity_labels = config.get_setting("severity_labels", {})
|
|
308
309
|
|
|
309
310
|
async with GitHubIntegration() as github:
|
|
310
|
-
commenter = PRCommenter(
|
|
311
|
+
commenter = PRCommenter(
|
|
312
|
+
github,
|
|
313
|
+
fail_on_severities=fail_on_severities,
|
|
314
|
+
severity_labels=severity_labels,
|
|
315
|
+
)
|
|
311
316
|
success = await commenter.post_findings_to_pr(
|
|
312
317
|
report,
|
|
313
318
|
create_review=getattr(args, "github_review", False),
|
|
@@ -426,12 +431,17 @@ Examples:
|
|
|
426
431
|
from iam_validator.core.config.config_loader import ConfigLoader
|
|
427
432
|
from iam_validator.core.pr_commenter import PRCommenter
|
|
428
433
|
|
|
429
|
-
# Load config to get fail_on_severity
|
|
434
|
+
# Load config to get fail_on_severity and severity_labels settings
|
|
430
435
|
config = ConfigLoader.load_config(config_path)
|
|
431
436
|
fail_on_severities = config.get_setting("fail_on_severity", ["error", "critical"])
|
|
437
|
+
severity_labels = config.get_setting("severity_labels", {})
|
|
432
438
|
|
|
433
439
|
async with GitHubIntegration() as github:
|
|
434
|
-
commenter = PRCommenter(
|
|
440
|
+
commenter = PRCommenter(
|
|
441
|
+
github,
|
|
442
|
+
fail_on_severities=fail_on_severities,
|
|
443
|
+
severity_labels=severity_labels,
|
|
444
|
+
)
|
|
435
445
|
success = await commenter.post_findings_to_pr(
|
|
436
446
|
report,
|
|
437
447
|
create_review=False, # Already posted per-file reviews in streaming mode
|
|
@@ -75,6 +75,16 @@ DEFAULT_CONFIG = {
|
|
|
75
75
|
# IAM Validity: error, warning, info
|
|
76
76
|
# Security: critical, high, medium, low
|
|
77
77
|
"fail_on_severity": list(constants.HIGH_SEVERITY_LEVELS),
|
|
78
|
+
# GitHub PR label mapping based on severity findings
|
|
79
|
+
# When issues with these severities are found, apply the corresponding labels
|
|
80
|
+
# If no issues with these severities exist, remove the labels if present
|
|
81
|
+
# Supports both single labels and lists of labels per severity
|
|
82
|
+
# Examples:
|
|
83
|
+
# Single label per severity: {"error": "iam-validity-error", "critical": "security-critical"}
|
|
84
|
+
# Multiple labels per severity: {"error": ["iam-error", "needs-fix"], "critical": ["security-critical", "needs-review"]}
|
|
85
|
+
# Mixed: {"error": "iam-validity-error", "critical": ["security-critical", "needs-review"]}
|
|
86
|
+
# Default: {} (disabled)
|
|
87
|
+
"severity_labels": {},
|
|
78
88
|
},
|
|
79
89
|
# ========================================================================
|
|
80
90
|
# AWS IAM Validation Checks (17 checks total)
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""Label Manager for GitHub PR Labels based on Severity Findings.
|
|
2
|
+
|
|
3
|
+
This module manages GitHub PR labels based on IAM policy validation severity findings.
|
|
4
|
+
When validation finds issues with specific severities, it applies corresponding labels.
|
|
5
|
+
When those severities are not found, it removes the labels if present.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from iam_validator.core.models import PolicyValidationResult, ValidationReport
|
|
13
|
+
from iam_validator.integrations.github_integration import GitHubIntegration
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class LabelManager:
|
|
19
|
+
"""Manages GitHub PR labels based on severity findings."""
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
github: "GitHubIntegration",
|
|
24
|
+
severity_labels: dict[str, str | list[str]] | None = None,
|
|
25
|
+
):
|
|
26
|
+
"""Initialize label manager.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
github: GitHubIntegration instance for API calls
|
|
30
|
+
severity_labels: Mapping of severity levels to label name(s)
|
|
31
|
+
Supports both single labels and lists of labels per severity.
|
|
32
|
+
Examples:
|
|
33
|
+
- Single label per severity:
|
|
34
|
+
{"error": "iam-validity-error", "critical": "security-critical"}
|
|
35
|
+
- Multiple labels per severity:
|
|
36
|
+
{"error": ["iam-error", "needs-fix"], "critical": ["security-critical", "needs-security-review"]}
|
|
37
|
+
- Mixed:
|
|
38
|
+
{"error": "iam-validity-error", "critical": ["security-critical", "needs-review"]}
|
|
39
|
+
"""
|
|
40
|
+
self.github = github
|
|
41
|
+
self.severity_labels = severity_labels or {}
|
|
42
|
+
|
|
43
|
+
def is_enabled(self) -> bool:
|
|
44
|
+
"""Check if label management is enabled.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
True if severity_labels is configured and GitHub is configured
|
|
48
|
+
"""
|
|
49
|
+
return bool(self.severity_labels) and self.github.is_configured()
|
|
50
|
+
|
|
51
|
+
def _get_severities_in_results(self, results: list["PolicyValidationResult"]) -> set[str]:
|
|
52
|
+
"""Extract all severity levels found in validation results.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
results: List of PolicyValidationResult objects
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
Set of severity levels found (e.g., {"error", "critical", "high"})
|
|
59
|
+
"""
|
|
60
|
+
severities = set()
|
|
61
|
+
for result in results:
|
|
62
|
+
for issue in result.issues:
|
|
63
|
+
severities.add(issue.severity)
|
|
64
|
+
return severities
|
|
65
|
+
|
|
66
|
+
def _get_severities_in_report(self, report: "ValidationReport") -> set[str]:
|
|
67
|
+
"""Extract all severity levels found in validation report.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
report: ValidationReport object
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Set of severity levels found (e.g., {"error", "critical", "high"})
|
|
74
|
+
"""
|
|
75
|
+
return self._get_severities_in_results(report.results)
|
|
76
|
+
|
|
77
|
+
def _determine_labels_to_apply(self, found_severities: set[str]) -> set[str]:
|
|
78
|
+
"""Determine which labels should be applied based on found severities.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
found_severities: Set of severity levels found in validation
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
Set of label names to apply
|
|
85
|
+
"""
|
|
86
|
+
labels_to_apply = set()
|
|
87
|
+
for severity, labels in self.severity_labels.items():
|
|
88
|
+
if severity in found_severities:
|
|
89
|
+
# Support both single labels and lists of labels
|
|
90
|
+
if isinstance(labels, list):
|
|
91
|
+
labels_to_apply.update(labels)
|
|
92
|
+
else:
|
|
93
|
+
labels_to_apply.add(labels)
|
|
94
|
+
return labels_to_apply
|
|
95
|
+
|
|
96
|
+
def _determine_labels_to_remove(self, found_severities: set[str]) -> set[str]:
|
|
97
|
+
"""Determine which labels should be removed based on missing severities.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
found_severities: Set of severity levels found in validation
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Set of label names to remove
|
|
104
|
+
"""
|
|
105
|
+
labels_to_remove = set()
|
|
106
|
+
for severity, labels in self.severity_labels.items():
|
|
107
|
+
if severity not in found_severities:
|
|
108
|
+
# Support both single labels and lists of labels
|
|
109
|
+
if isinstance(labels, list):
|
|
110
|
+
labels_to_remove.update(labels)
|
|
111
|
+
else:
|
|
112
|
+
labels_to_remove.add(labels)
|
|
113
|
+
return labels_to_remove
|
|
114
|
+
|
|
115
|
+
async def manage_labels_from_results(
|
|
116
|
+
self, results: list["PolicyValidationResult"]
|
|
117
|
+
) -> tuple[bool, int, int]:
|
|
118
|
+
"""Manage PR labels based on validation results.
|
|
119
|
+
|
|
120
|
+
This method will:
|
|
121
|
+
1. Determine which severity levels are present in the results
|
|
122
|
+
2. Add labels for severities that are found
|
|
123
|
+
3. Remove labels for severities that are not found
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
results: List of PolicyValidationResult objects
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
Tuple of (success, labels_added, labels_removed)
|
|
130
|
+
"""
|
|
131
|
+
if not self.is_enabled():
|
|
132
|
+
logger.debug("Label management not enabled (no severity_labels configured)")
|
|
133
|
+
return (True, 0, 0)
|
|
134
|
+
|
|
135
|
+
# Get all severities found in results
|
|
136
|
+
found_severities = self._get_severities_in_results(results)
|
|
137
|
+
logger.debug(f"Found severities in results: {found_severities}")
|
|
138
|
+
|
|
139
|
+
# Determine which labels to apply/remove
|
|
140
|
+
labels_to_apply = self._determine_labels_to_apply(found_severities)
|
|
141
|
+
labels_to_remove = self._determine_labels_to_remove(found_severities)
|
|
142
|
+
|
|
143
|
+
logger.debug(f"Labels to apply: {labels_to_apply}")
|
|
144
|
+
logger.debug(f"Labels to remove: {labels_to_remove}")
|
|
145
|
+
|
|
146
|
+
# Get current labels on PR
|
|
147
|
+
current_labels = set(await self.github.get_labels())
|
|
148
|
+
logger.debug(f"Current PR labels: {current_labels}")
|
|
149
|
+
|
|
150
|
+
# Filter: only add labels that aren't already present
|
|
151
|
+
labels_to_add = labels_to_apply - current_labels
|
|
152
|
+
|
|
153
|
+
# Filter: only remove labels that are currently present
|
|
154
|
+
labels_to_actually_remove = labels_to_remove & current_labels
|
|
155
|
+
|
|
156
|
+
success = True
|
|
157
|
+
added_count = 0
|
|
158
|
+
removed_count = 0
|
|
159
|
+
|
|
160
|
+
# Add new labels
|
|
161
|
+
if labels_to_add:
|
|
162
|
+
logger.info(f"Adding labels to PR: {labels_to_add}")
|
|
163
|
+
if await self.github.add_labels(list(labels_to_add)):
|
|
164
|
+
added_count = len(labels_to_add)
|
|
165
|
+
else:
|
|
166
|
+
logger.error("Failed to add labels to PR")
|
|
167
|
+
success = False
|
|
168
|
+
|
|
169
|
+
# Remove old labels
|
|
170
|
+
for label in labels_to_actually_remove:
|
|
171
|
+
logger.info(f"Removing label from PR: {label}")
|
|
172
|
+
if await self.github.remove_label(label):
|
|
173
|
+
removed_count += 1
|
|
174
|
+
else:
|
|
175
|
+
logger.error(f"Failed to remove label: {label}")
|
|
176
|
+
success = False
|
|
177
|
+
|
|
178
|
+
if added_count > 0 or removed_count > 0:
|
|
179
|
+
logger.info(f"Label management complete: added {added_count}, removed {removed_count}")
|
|
180
|
+
else:
|
|
181
|
+
logger.debug("No label changes needed")
|
|
182
|
+
|
|
183
|
+
return (success, added_count, removed_count)
|
|
184
|
+
|
|
185
|
+
async def manage_labels_from_report(self, report: "ValidationReport") -> tuple[bool, int, int]:
|
|
186
|
+
"""Manage PR labels based on validation report.
|
|
187
|
+
|
|
188
|
+
This is a convenience method that extracts results from the report
|
|
189
|
+
and calls manage_labels_from_results().
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
report: ValidationReport object
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
Tuple of (success, labels_added, labels_removed)
|
|
196
|
+
"""
|
|
197
|
+
return await self.manage_labels_from_results(report.results)
|
|
@@ -13,7 +13,9 @@ from iam_validator.core.constants import (
|
|
|
13
13
|
REVIEW_IDENTIFIER,
|
|
14
14
|
SUMMARY_IDENTIFIER,
|
|
15
15
|
)
|
|
16
|
+
from iam_validator.core.label_manager import LabelManager
|
|
16
17
|
from iam_validator.core.models import ValidationIssue, ValidationReport
|
|
18
|
+
from iam_validator.core.report import ReportGenerator
|
|
17
19
|
from iam_validator.integrations.github_integration import GitHubIntegration, ReviewEvent
|
|
18
20
|
|
|
19
21
|
logger = logging.getLogger(__name__)
|
|
@@ -32,6 +34,7 @@ class PRCommenter:
|
|
|
32
34
|
github: GitHubIntegration | None = None,
|
|
33
35
|
cleanup_old_comments: bool = True,
|
|
34
36
|
fail_on_severities: list[str] | None = None,
|
|
37
|
+
severity_labels: dict[str, str | list[str]] | None = None,
|
|
35
38
|
):
|
|
36
39
|
"""Initialize PR commenter.
|
|
37
40
|
|
|
@@ -40,16 +43,24 @@ class PRCommenter:
|
|
|
40
43
|
cleanup_old_comments: Whether to clean up old bot comments before posting new ones
|
|
41
44
|
fail_on_severities: List of severity levels that should trigger REQUEST_CHANGES
|
|
42
45
|
(e.g., ["error", "critical", "high"])
|
|
46
|
+
severity_labels: Mapping of severity levels to label name(s) for automatic label management
|
|
47
|
+
Supports both single labels and lists of labels per severity.
|
|
48
|
+
Examples:
|
|
49
|
+
- Single: {"error": "iam-validity-error", "critical": "security-critical"}
|
|
50
|
+
- Multiple: {"error": ["iam-error", "needs-fix"], "critical": ["security-critical", "needs-review"]}
|
|
51
|
+
- Mixed: {"error": "iam-validity-error", "critical": ["security-critical", "needs-review"]}
|
|
43
52
|
"""
|
|
44
53
|
self.github = github
|
|
45
54
|
self.cleanup_old_comments = cleanup_old_comments
|
|
46
55
|
self.fail_on_severities = fail_on_severities or ["error", "critical"]
|
|
56
|
+
self.severity_labels = severity_labels or {}
|
|
47
57
|
|
|
48
58
|
async def post_findings_to_pr(
|
|
49
59
|
self,
|
|
50
60
|
report: ValidationReport,
|
|
51
61
|
create_review: bool = True,
|
|
52
62
|
add_summary_comment: bool = True,
|
|
63
|
+
manage_labels: bool = True,
|
|
53
64
|
) -> bool:
|
|
54
65
|
"""Post validation findings to a PR.
|
|
55
66
|
|
|
@@ -57,6 +68,7 @@ class PRCommenter:
|
|
|
57
68
|
report: Validation report with findings
|
|
58
69
|
create_review: Whether to create a PR review with line comments
|
|
59
70
|
add_summary_comment: Whether to add a summary comment
|
|
71
|
+
manage_labels: Whether to manage PR labels based on severity findings
|
|
60
72
|
|
|
61
73
|
Returns:
|
|
62
74
|
True if successful, False otherwise
|
|
@@ -81,8 +93,6 @@ class PRCommenter:
|
|
|
81
93
|
|
|
82
94
|
# Post summary comment (potentially as multiple parts)
|
|
83
95
|
if add_summary_comment:
|
|
84
|
-
from iam_validator.core.report import ReportGenerator
|
|
85
|
-
|
|
86
96
|
generator = ReportGenerator()
|
|
87
97
|
comment_parts = generator.generate_github_comment_parts(report)
|
|
88
98
|
|
|
@@ -104,6 +114,18 @@ class PRCommenter:
|
|
|
104
114
|
logger.error("Failed to post review comments")
|
|
105
115
|
success = False
|
|
106
116
|
|
|
117
|
+
# Manage PR labels based on severity findings
|
|
118
|
+
if manage_labels and self.severity_labels:
|
|
119
|
+
label_manager = LabelManager(self.github, self.severity_labels)
|
|
120
|
+
label_success, added, removed = await label_manager.manage_labels_from_report(report)
|
|
121
|
+
|
|
122
|
+
if not label_success:
|
|
123
|
+
logger.error("Failed to manage PR labels")
|
|
124
|
+
success = False
|
|
125
|
+
else:
|
|
126
|
+
if added > 0 or removed > 0:
|
|
127
|
+
logger.info(f"Label management: added {added}, removed {removed}")
|
|
128
|
+
|
|
107
129
|
return success
|
|
108
130
|
|
|
109
131
|
async def _post_review_comments(self, report: ValidationReport) -> bool:
|
|
@@ -288,7 +310,7 @@ class PRCommenter:
|
|
|
288
310
|
|
|
289
311
|
return mapping
|
|
290
312
|
|
|
291
|
-
except Exception as e:
|
|
313
|
+
except Exception as e: # pylint: disable=broad-exception-caught
|
|
292
314
|
logger.warning(f"Could not parse {policy_file} for line mapping: {e}")
|
|
293
315
|
return {}
|
|
294
316
|
|
|
@@ -369,7 +391,7 @@ class PRCommenter:
|
|
|
369
391
|
|
|
370
392
|
return None
|
|
371
393
|
|
|
372
|
-
except Exception as e:
|
|
394
|
+
except Exception as e: # pylint: disable=broad-exception-caught
|
|
373
395
|
logger.debug(f"Could not search {policy_file}: {e}")
|
|
374
396
|
return None
|
|
375
397
|
|
|
@@ -398,15 +420,20 @@ async def post_report_to_pr(
|
|
|
398
420
|
|
|
399
421
|
report = ValidationReport.model_validate(report_data)
|
|
400
422
|
|
|
401
|
-
# Load config to get fail_on_severity
|
|
423
|
+
# Load config to get fail_on_severity and severity_labels settings
|
|
402
424
|
from iam_validator.core.config.config_loader import ConfigLoader
|
|
403
425
|
|
|
404
426
|
config = ConfigLoader.load_config(config_path)
|
|
405
427
|
fail_on_severities = config.get_setting("fail_on_severity", ["error", "critical"])
|
|
428
|
+
severity_labels = config.get_setting("severity_labels", {})
|
|
406
429
|
|
|
407
430
|
# Post to PR
|
|
408
431
|
async with GitHubIntegration() as github:
|
|
409
|
-
commenter = PRCommenter(
|
|
432
|
+
commenter = PRCommenter(
|
|
433
|
+
github,
|
|
434
|
+
fail_on_severities=fail_on_severities,
|
|
435
|
+
severity_labels=severity_labels,
|
|
436
|
+
)
|
|
410
437
|
return await commenter.post_findings_to_pr(
|
|
411
438
|
report,
|
|
412
439
|
create_review=create_review,
|
|
@@ -419,6 +446,6 @@ async def post_report_to_pr(
|
|
|
419
446
|
except json.JSONDecodeError as e:
|
|
420
447
|
logger.error(f"Invalid JSON in report file: {e}")
|
|
421
448
|
return False
|
|
422
|
-
except Exception as e:
|
|
449
|
+
except Exception as e: # pylint: disable=broad-exception-caught
|
|
423
450
|
logger.error(f"Failed to post report to PR: {e}")
|
|
424
451
|
return False
|
|
File without changes
|
{iam_policy_validator-1.9.0.dist-info → iam_policy_validator-1.10.0.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{iam_policy_validator-1.9.0.dist-info → iam_policy_validator-1.10.0.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|