Refactor code structure and optimize performance across multiple modules

This commit is contained in:
StellaOps Bot
2025-12-26 20:03:22 +02:00
parent c786faae84
commit f10d83c444
1385 changed files with 69732 additions and 10280 deletions

View File

@@ -0,0 +1,448 @@
# SCM Connector Plugins
> **Sprint:** SPRINT_20251226_016_AI_remedy_autopilot
> **Tasks:** REMEDY-08 through REMEDY-14
This guide documents the SCM (Source Control Management) connector plugin architecture for automated remediation PR generation.
## Overview
StellaOps supports automated Pull Request generation for remediation plans across multiple SCM platforms. The plugin architecture enables customer-premise integrations with:
- **GitHub** (github.com and GitHub Enterprise Server)
- **GitLab** (gitlab.com and self-hosted)
- **Azure DevOps** (Services and Server)
- **Gitea** (including Forgejo and Codeberg)
## Architecture
### Plugin Interface
```csharp
public interface IScmConnectorPlugin
{
string ScmType { get; } // "github", "gitlab", "azuredevops", "gitea"
string DisplayName { get; } // Human-readable name
bool IsAvailable(ScmConnectorOptions options); // Check if configured
bool CanHandle(string repositoryUrl); // Auto-detect from URL
IScmConnector Create(ScmConnectorOptions options, HttpClient httpClient);
}
```
### Connector Interface
```csharp
public interface IScmConnector
{
string ScmType { get; }
// Branch operations
Task<BranchResult> CreateBranchAsync(
string owner, string repo, string branchName, string baseBranch, ...);
// File operations
Task<FileUpdateResult> UpdateFileAsync(
string owner, string repo, string branch, string filePath,
string content, string commitMessage, ...);
// Pull request operations
Task<PrCreateResult> CreatePullRequestAsync(
string owner, string repo, string headBranch, string baseBranch,
string title, string body, ...);
Task<PrStatusResult> GetPullRequestStatusAsync(...);
Task<bool> UpdatePullRequestAsync(...);
Task<bool> AddCommentAsync(...);
Task<bool> ClosePullRequestAsync(...);
// CI status
Task<CiStatusResult> GetCiStatusAsync(
string owner, string repo, string commitSha, ...);
}
```
### Catalog and Factory
```csharp
public sealed class ScmConnectorCatalog
{
// Get connector by explicit type
IScmConnector? GetConnector(string scmType, ScmConnectorOptions options);
// Auto-detect SCM type from repository URL
IScmConnector? GetConnectorForRepository(string repositoryUrl, ScmConnectorOptions options);
// List all available plugins
IReadOnlyList<IScmConnectorPlugin> Plugins { get; }
}
```
## Configuration
### Sample Configuration
```yaml
scmConnectors:
timeoutSeconds: 30
userAgent: "StellaOps.AdvisoryAI.Remediation/1.0"
github:
enabled: true
baseUrl: "" # Default: https://api.github.com
apiToken: "${GITHUB_PAT}"
gitlab:
enabled: true
baseUrl: "" # Default: https://gitlab.com/api/v4
apiToken: "${GITLAB_PAT}"
azuredevops:
enabled: true
baseUrl: "" # Default: https://dev.azure.com
apiToken: "${AZURE_DEVOPS_PAT}"
gitea:
enabled: true
baseUrl: "https://git.example.com" # Required
apiToken: "${GITEA_TOKEN}"
```
### Environment Variables
| Variable | Description |
|----------|-------------|
| `STELLAOPS_SCM_GITHUB_TOKEN` | GitHub PAT or App token |
| `STELLAOPS_SCM_GITLAB_TOKEN` | GitLab Personal/Project token |
| `STELLAOPS_SCM_AZUREDEVOPS_TOKEN` | Azure DevOps PAT |
| `STELLAOPS_SCM_GITEA_TOKEN` | Gitea application token |
### Required Token Scopes
| Platform | Required Scopes |
|----------|-----------------|
| **GitHub** | `repo`, `workflow` (PAT) or `contents:write`, `pull_requests:write`, `checks:read` (App) |
| **GitLab** | `api`, `read_repository`, `write_repository` |
| **Azure DevOps** | Code (Read & Write), Pull Request Contribute, Build (Read) |
| **Gitea** | `repo` (full repository access) |
## Connector Details
### GitHub Connector
```yaml
github:
enabled: true
baseUrl: "" # Leave empty for github.com
apiToken: "${GITHUB_PAT}"
```
**Features:**
- Bearer token authentication
- Check-runs API for CI status (GitHub Actions)
- Combined commit status support
- Enterprise Server support via `baseUrl`
**API Endpoints Used:**
- `GET /repos/{owner}/{repo}/git/refs/heads/{branch}` - Get branch SHA
- `POST /repos/{owner}/{repo}/git/refs` - Create branch
- `PUT /repos/{owner}/{repo}/contents/{path}` - Update file
- `POST /repos/{owner}/{repo}/pulls` - Create PR
- `GET /repos/{owner}/{repo}/commits/{sha}/check-runs` - CI status
### GitLab Connector
```yaml
gitlab:
enabled: true
baseUrl: "" # Leave empty for gitlab.com
apiToken: "${GITLAB_PAT}"
```
**Features:**
- PRIVATE-TOKEN header authentication
- Merge Request creation (GitLab terminology)
- Pipeline and Jobs API for CI status
- Self-hosted instance support
**API Endpoints Used:**
- `POST /projects/{id}/repository/branches` - Create branch
- `POST /projects/{id}/repository/commits` - Commit file changes
- `POST /projects/{id}/merge_requests` - Create MR
- `GET /projects/{id}/pipelines?sha={sha}` - CI status
- `GET /projects/{id}/pipelines/{id}/jobs` - Job details
### Azure DevOps Connector
```yaml
azuredevops:
enabled: true
baseUrl: "" # Leave empty for Azure DevOps Services
apiToken: "${AZURE_DEVOPS_PAT}"
apiVersion: "7.1"
```
**Features:**
- Basic authentication with PAT (empty username, token as password)
- Push API for atomic commits
- Azure Pipelines build status
- Azure DevOps Server support
**API Endpoints Used:**
- `GET /{org}/{project}/_apis/git/refs` - Get branch refs
- `POST /{org}/{project}/_apis/git/refs` - Create branch
- `POST /{org}/{project}/_apis/git/pushes` - Commit changes
- `POST /{org}/{project}/_apis/git/pullrequests` - Create PR
- `GET /{org}/{project}/_apis/build/builds` - Build status
### Gitea Connector
```yaml
gitea:
enabled: true
baseUrl: "https://git.example.com" # Required
apiToken: "${GITEA_TOKEN}"
```
**Features:**
- Token header authentication
- Gitea Actions support (workflow runs)
- Compatible with Forgejo and Codeberg
- Combined commit status API
**API Endpoints Used:**
- `GET /api/v1/repos/{owner}/{repo}/branches/{branch}` - Get branch
- `POST /api/v1/repos/{owner}/{repo}/branches` - Create branch
- `PUT /api/v1/repos/{owner}/{repo}/contents/{path}` - Update file
- `POST /api/v1/repos/{owner}/{repo}/pulls` - Create PR
- `GET /api/v1/repos/{owner}/{repo}/commits/{sha}/status` - Status
- `GET /api/v1/repos/{owner}/{repo}/actions/runs` - Workflow runs
## Usage
### Dependency Injection
```csharp
// In Startup.cs or Program.cs
services.AddScmConnectors(config =>
{
// Optionally add custom plugins
config.AddPlugin(new CustomScmConnectorPlugin());
// Or remove built-in plugins
config.RemovePlugin("github");
});
```
### Creating a Connector
```csharp
public class RemediationService
{
private readonly ScmConnectorCatalog _catalog;
public async Task<PrCreateResult> CreateRemediationPrAsync(
string repositoryUrl,
RemediationPlan plan,
CancellationToken cancellationToken)
{
var options = new ScmConnectorOptions
{
ApiToken = _configuration["ScmToken"],
BaseUrl = _configuration["ScmBaseUrl"]
};
// Auto-detect connector from URL
var connector = _catalog.GetConnectorForRepository(repositoryUrl, options);
if (connector is null)
throw new InvalidOperationException($"No connector available for {repositoryUrl}");
// Create branch
var branchResult = await connector.CreateBranchAsync(
owner: "myorg",
repo: "myrepo",
branchName: $"stellaops/remediation/{plan.Id}",
baseBranch: "main",
cancellationToken);
// Update files
foreach (var change in plan.FileChanges)
{
await connector.UpdateFileAsync(
owner: "myorg",
repo: "myrepo",
branch: branchResult.BranchName,
filePath: change.Path,
content: change.NewContent,
commitMessage: $"chore: apply remediation for {plan.FindingId}",
cancellationToken);
}
// Create PR
return await connector.CreatePullRequestAsync(
owner: "myorg",
repo: "myrepo",
headBranch: branchResult.BranchName,
baseBranch: "main",
title: $"[StellaOps] Remediation for {plan.FindingId}",
body: GeneratePrBody(plan),
cancellationToken);
}
}
```
### Polling CI Status
```csharp
public async Task<CiState> WaitForCiAsync(
IScmConnector connector,
string owner,
string repo,
string commitSha,
TimeSpan timeout,
CancellationToken cancellationToken)
{
var deadline = DateTime.UtcNow + timeout;
while (DateTime.UtcNow < deadline)
{
var status = await connector.GetCiStatusAsync(
owner, repo, commitSha, cancellationToken);
switch (status.OverallState)
{
case CiState.Success:
case CiState.Failure:
case CiState.Error:
return status.OverallState;
case CiState.Pending:
case CiState.Running:
await Task.Delay(TimeSpan.FromSeconds(30), cancellationToken);
break;
}
}
return CiState.Unknown;
}
```
## CI State Mapping
Different SCM platforms use different status values. The connector normalizes them:
| Platform | Pending | Running | Success | Failure | Error |
|----------|---------|---------|---------|---------|-------|
| **GitHub** | `pending`, `queued` | `in_progress` | `success` | `failure` | `error`, `cancelled` |
| **GitLab** | `pending`, `waiting` | `running` | `success` | `failed` | `canceled`, `skipped` |
| **Azure DevOps** | `notStarted`, `postponed` | `inProgress` | `succeeded` | `failed` | `canceled` |
| **Gitea** | `pending`, `queued` | `running` | `success` | `failure` | `cancelled`, `timed_out` |
## URL Auto-Detection
The `CanHandle` method on each plugin detects repository URLs:
| Plugin | URL Patterns |
|--------|--------------|
| **GitHub** | `github.com`, `github.` |
| **GitLab** | `gitlab.com`, `gitlab.` |
| **Azure DevOps** | `dev.azure.com`, `visualstudio.com`, `azure.com` |
| **Gitea** | `gitea.`, `forgejo.`, `codeberg.org` |
Example:
```csharp
// Auto-detects GitHub
var connector = catalog.GetConnectorForRepository(
"https://github.com/myorg/myrepo", options);
// Auto-detects GitLab
var connector = catalog.GetConnectorForRepository(
"https://gitlab.com/mygroup/myproject", options);
```
## Custom Plugins
To add support for a new SCM platform:
```csharp
public sealed class BitbucketScmConnectorPlugin : IScmConnectorPlugin
{
public string ScmType => "bitbucket";
public string DisplayName => "Bitbucket";
public bool IsAvailable(ScmConnectorOptions options) =>
!string.IsNullOrEmpty(options.ApiToken);
public bool CanHandle(string repositoryUrl) =>
repositoryUrl.Contains("bitbucket.org", StringComparison.OrdinalIgnoreCase);
public IScmConnector Create(ScmConnectorOptions options, HttpClient httpClient) =>
new BitbucketScmConnector(httpClient, options);
}
public sealed class BitbucketScmConnector : ScmConnectorBase
{
// Implement abstract methods...
}
```
Register the custom plugin:
```csharp
services.AddScmConnectors(config =>
{
config.AddPlugin(new BitbucketScmConnectorPlugin());
});
```
## Error Handling
All connector methods return result objects with `Success` and `ErrorMessage`:
```csharp
var result = await connector.CreateBranchAsync(...);
if (!result.Success)
{
_logger.LogError("Failed to create branch: {Error}", result.ErrorMessage);
return;
}
// Continue with successful result
var branchSha = result.CommitSha;
```
## Security Considerations
1. **Token Storage**: Never store tokens in configuration files. Use environment variables or secret management.
2. **Minimum Permissions**: Request only required scopes for each platform.
3. **TLS Verification**: Always verify TLS certificates in production (`verifySsl: true`).
4. **Audit Logging**: All SCM operations are logged for compliance.
5. **Repository Access**: Connectors only access repositories explicitly provided. No enumeration of accessible repos.
## Telemetry
SCM operations emit structured logs:
```json
{
"timestamp": "2025-12-26T10:30:00Z",
"operation": "scm_create_pr",
"scmType": "github",
"owner": "myorg",
"repo": "myrepo",
"branch": "stellaops/remediation/plan-123",
"duration_ms": 1234,
"success": true,
"pr_number": 456,
"pr_url": "https://github.com/myorg/myrepo/pull/456"
}
```
## Related Documentation
- [Remediation API](../remediation-api.md)
- [AI Attestations](./ai-attestations.md)
- [Offline Model Bundles](./offline-model-bundles.md)
- [Configuration Reference](../../../../etc/scm-connectors.yaml.sample)