Skip to content

Commit 1ea702b

Browse files
committed
feat(actions): implement automated policy violation actions
- Add complete ActionService with issue creation and repository archiving - Implement duplicate issue prevention with GitHub API integration - Add IssueDetails model for configurable issue templates - Replace LoggingActionService with full ActionService implementation - Add comprehensive action logging to ActionLog database table - Update PolicyConfig to support issue details configuration - Add sample .github/config.yaml with policy examples - Add comprehensive documentation for action service - Remove test endpoints (/verify-scan, /log-job) in favor of UI controls BREAKING CHANGE: LoggingActionService removed, replaced with ActionService
1 parent 9099ac4 commit 1ea702b

26 files changed

+1042
-343
lines changed
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
# Implement Automated Actions for Policy Violations
2+
3+
## Overview
4+
5+
Replace the `LoggingActionService` with a full implementation that executes configured actions (create-issue, archive-repo) for policy violations, with proper duplicate prevention, error handling, and audit logging.
6+
7+
## Implementation Steps
8+
9+
### 1. Create IssueDetails Model
10+
11+
**File**: `10xGitHubPolicies.App/Services/Configuration/Models/IssueDetails.cs`
12+
13+
Create a new model class to represent issue configuration:
14+
15+
```csharp
16+
public class IssueDetails
17+
{
18+
[YamlMember(Alias = "title")]
19+
public string Title { get; set; }
20+
21+
[YamlMember(Alias = "body")]
22+
public string Body { get; set; }
23+
24+
[YamlMember(Alias = "labels")]
25+
public List<string> Labels { get; set; } = new();
26+
}
27+
```
28+
29+
### 2. Update PolicyConfig Model
30+
31+
**File**: `10xGitHubPolicies.App/Services/Configuration/Models/PolicyConfig.cs`
32+
33+
Add missing properties to match the documented configuration format:
34+
35+
```csharp
36+
public class PolicyConfig
37+
{
38+
[YamlMember(Alias = "name")]
39+
public string Name { get; set; }
40+
41+
[YamlMember(Alias = "type")]
42+
public string Type { get; set; }
43+
44+
[YamlMember(Alias = "action")]
45+
public string Action { get; set; }
46+
47+
[YamlMember(Alias = "issue_details")]
48+
public IssueDetails IssueDetails { get; set; }
49+
}
50+
```
51+
52+
### 3. Implement ActionService
53+
54+
**File**: `10xGitHubPolicies.App/Services/Action/ActionService.cs`
55+
56+
Create a new implementation that replaces `LoggingActionService`:
57+
58+
**Key responsibilities:**
59+
60+
- Retrieve violations for the scan from database
61+
- Load policy configuration to determine actions
62+
- For each violation:
63+
- Match violation to policy configuration by PolicyType
64+
- Execute the configured action (create-issue or archive-repo)
65+
- Log the action to ActionLog table
66+
- Handle errors gracefully
67+
68+
**Core logic:**
69+
70+
```csharp
71+
public async Task ProcessActionsForScanAsync(int scanId)
72+
{
73+
// 1. Load violations with related entities (Repository, Policy)
74+
var violations = await _dbContext.PolicyViolations
75+
.Include(v => v.Repository)
76+
.Include(v => v.Policy)
77+
.Where(v => v.ScanId == scanId)
78+
.ToListAsync();
79+
80+
// 2. Get configuration to determine actions
81+
var config = await _configurationService.GetConfigAsync();
82+
83+
// 3. Process each violation
84+
foreach (var violation in violations)
85+
{
86+
var policyConfig = config.Policies
87+
.FirstOrDefault(p => p.Type == violation.Policy.PolicyKey);
88+
89+
if (policyConfig == null) continue;
90+
91+
// 4. Execute action based on configuration
92+
if (policyConfig.Action == "create-issue")
93+
{
94+
await CreateIssueForViolationAsync(violation, policyConfig);
95+
}
96+
else if (policyConfig.Action == "archive-repo")
97+
{
98+
await ArchiveRepositoryForViolationAsync(violation, policyConfig);
99+
}
100+
}
101+
}
102+
```
103+
104+
**Duplicate Prevention for Issues (US-010):**
105+
106+
- Before creating an issue, check for existing open issues with the same title and label
107+
- Use `IGitHubService` to search existing issues in the repository
108+
- Skip issue creation if duplicate found, log the skip action
109+
110+
**Error Handling:**
111+
112+
- Wrap each action in try-catch to prevent one failure from blocking others
113+
- Log failures with details to ActionLog table with Status = "Failed"
114+
- Continue processing remaining violations even if one fails
115+
116+
### 4. Update Service Registration
117+
118+
**File**: `10xGitHubPolicies.App/Program.cs`
119+
120+
Replace the service registration:
121+
122+
```csharp
123+
// Change from:
124+
builder.Services.AddScoped<IActionService, LoggingActionService>();
125+
126+
// To:
127+
builder.Services.AddScoped<IActionService, ActionService>();
128+
```
129+
130+
### 5. Add GitHub Service Methods (if missing)
131+
132+
**File**: `10xGitHubPolicies.App/Services/GitHub/IGitHubService.cs` and `GitHubService.cs`
133+
134+
Verify these methods exist (they should based on documentation):
135+
136+
- `Task<Issue> CreateIssueAsync(long repositoryId, string title, string body, IEnumerable<string> labels)`
137+
- `Task ArchiveRepositoryAsync(long repositoryId)`
138+
139+
If a method to check for existing issues doesn't exist, add:
140+
141+
- `Task<IReadOnlyList<Issue>> GetOpenIssuesAsync(long repositoryId, string label)`
142+
143+
### 6. Update ActionLog Usage
144+
145+
Ensure ActionLog entries are created for each action with:
146+
147+
- `ActionType`: "create-issue" or "archive-repo"
148+
- `Status`: "Success", "Failed", or "Skipped"
149+
- `Details`: JSON or text with action details/error messages
150+
- `Timestamp`: DateTime.UtcNow
151+
152+
### 7. Delete LoggingActionService
153+
154+
**File**: `10xGitHubPolicies.App/Services/Action/LoggingActionService.cs`
155+
156+
Remove this file after ActionService is implemented and registered.
157+
158+
### 8. Cleanup Test Endpoints
159+
160+
**File**: `10xGitHubPolicies.App/Program.cs`
161+
162+
Remove the test/verification endpoints that are no longer needed:
163+
164+
- Remove `/verify-scan` endpoint (lines 78-90) - functionality exists in UI via "Scan Now" button
165+
- Remove `/log-job` endpoint (lines 93-97) - was only for testing Hangfire
166+
167+
## Key Considerations
168+
169+
**Dependencies:**
170+
171+
- `IGitHubService`: For GitHub API operations
172+
- `IConfigurationService`: To retrieve policy configurations
173+
- `ApplicationDbContext`: For database operations
174+
- `ILogger<ActionService>`: For logging
175+
176+
**Service Lifetime:** Scoped (matches current registration)
177+
178+
**Thread Safety:** Not required - Hangfire processes jobs sequentially per worker
179+
180+
**Idempotency:** Actions should be safe to retry (duplicate prevention for issues, archive is idempotent)
181+
182+
**Testing:** After implementation, test via:
183+
184+
1. Use the "Scan Now" button in the UI dashboard to trigger a scan
185+
2. Monitor Hangfire dashboard at `/hangfire` for job execution
186+
3. Check ActionLog table for action records
187+
4. Verify GitHub issues created and repositories archived
188+
189+
### To-dos
190+
191+
- [ ] Create IssueDetails model class for YAML configuration
192+
- [ ] Add Name and IssueDetails properties to PolicyConfig model
193+
- [ ] Create ActionService with create-issue and archive-repo logic
194+
- [ ] Implement duplicate issue detection logic in ActionService
195+
- [ ] Replace LoggingActionService with ActionService in Program.cs
196+
- [ ] Verify and add any missing GitHub service methods for issue checking
197+
- [ ] Delete LoggingActionService.cs file
198+
- [ ] Remove test endpoints (/verify-scan and /log-job) from Program.cs

.github/config.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Access control: Specify the GitHub team authorized to access the dashboard.
2+
# Format: 'organization-slug/team-slug'
3+
access_control:
4+
authorized_team: 'my-org/security-team'
5+
6+
# Policies: Define the rules to enforce across your organization's repositories.
7+
policies:
8+
- name: 'Check for AGENTS.md'
9+
type: 'has_agents_md'
10+
action: 'create_issue'
11+
issue_details:
12+
title: 'Compliance: AGENTS.md file is missing'
13+
body: 'This repository is missing the AGENTS.md file in its root directory. Please add this file to comply with organization standards.'
14+
labels: ['policy-violation', 'documentation']
15+
16+
- name: 'Check for catalog-info.yaml'
17+
type: 'has_catalog_info_yaml'
18+
action: 'create_issue'
19+
issue_details:
20+
title: 'Compliance: catalog-info.yaml is missing'
21+
body: 'This repository is missing the catalog-info.yaml file. This file is required for backstage.io service discovery.'
22+
labels: ['policy-violation', 'backstage']
23+
24+
- name: 'Verify Workflow Permissions'
25+
type: 'correct_workflow_permissions'
26+
action: 'log_only'

10xGitHubPolicies.App/Program.cs

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
using _10xGitHubPolicies.App.Services.Policies;
88
using _10xGitHubPolicies.App.Services.Policies.Evaluators;
99
using _10xGitHubPolicies.App.Services.Scanning;
10+
1011
using Hangfire;
12+
1113
using Microsoft.AspNetCore.Components;
1214
using Microsoft.AspNetCore.Components.Web;
1315
using Microsoft.EntityFrameworkCore;
@@ -48,7 +50,7 @@
4850
builder.Services.AddScoped<IPolicyEvaluator, HasAgentsMdEvaluator>();
4951
builder.Services.AddScoped<IPolicyEvaluator, HasCatalogInfoYamlEvaluator>();
5052
builder.Services.AddScoped<IPolicyEvaluator, CorrectWorkflowPermissionsEvaluator>();
51-
builder.Services.AddScoped<IActionService, LoggingActionService>();
53+
builder.Services.AddScoped<IActionService, ActionService>();
5254

5355

5456
builder.Services.Configure<GitHubAppOptions>(builder.Configuration.GetSection(GitHubAppOptions.GitHubApp));
@@ -74,28 +76,6 @@
7476

7577
app.UseHangfireDashboard();
7678

77-
// Verification endpoint
78-
app.MapGet("/verify-scan", async (IScanningService scanningService, ILogger<Program> logger) =>
79-
{
80-
try
81-
{
82-
await scanningService.PerformScanAsync();
83-
return Results.Ok("Scan completed successfully. Check logs for details.");
84-
}
85-
catch (Exception ex)
86-
{
87-
logger.LogError(ex, "Failed to perform scan.");
88-
return Results.Problem("Failed to perform scan.");
89-
}
90-
});
91-
92-
// Endpoint to enqueue a test job
93-
app.MapGet("/log-job", (IBackgroundJobClient backgroundJobClient) =>
94-
{
95-
var jobId = backgroundJobClient.Enqueue(() => Console.WriteLine("Hello from a Hangfire job!"));
96-
return Results.Ok($"Job '{jobId}' enqueued.");
97-
});
98-
9979
app.MapBlazorHub();
10080
app.MapFallbackToPage("/_Host");
10181

0 commit comments

Comments
 (0)