Fix build and code structure improvements. New but essential UI functionality. CI improvements. Documentation improvements. AI module improvements.

This commit is contained in:
StellaOps Bot
2025-12-26 21:54:17 +02:00
parent 335ff7da16
commit c2b9cd8d1f
3717 changed files with 264714 additions and 48202 deletions

View File

@@ -0,0 +1,580 @@
# Plugin Development SDK
This guide covers the StellaOps Plugin SDK for developing custom plugins.
## Overview
The Plugin SDK provides:
- Base interfaces for all plugin types
- DI registration utilities
- Configuration binding helpers
- Version compatibility attributes
- Testing infrastructure
## Prerequisites
- .NET 10 SDK
- Understanding of dependency injection
- Familiarity with async/await patterns
## Core Interfaces
### IAvailabilityPlugin
Base interface for plugins with availability checks:
```csharp
namespace StellaOps.Plugin;
public interface IAvailabilityPlugin
{
/// <summary>
/// Checks whether the plugin is available in the current environment.
/// </summary>
bool IsAvailable(IServiceProvider services);
}
```
### IRouterTransportPlugin
Interface for router transport plugins:
```csharp
namespace StellaOps.Router.Common.Plugins;
public interface IRouterTransportPlugin
{
string TransportName { get; }
string DisplayName { get; }
bool IsAvailable(IServiceProvider services);
void Register(RouterTransportRegistrationContext context);
}
```
### IConcielierConnector
Interface for vulnerability data connectors:
```csharp
namespace StellaOps.Concelier.Core.Plugins;
public interface IConcielierConnector : IAvailabilityPlugin
{
string ConnectorId { get; }
string DisplayName { get; }
Task<IAsyncEnumerable<VulnerabilityRecord>> FetchAsync(
FetchOptions options,
CancellationToken cancellationToken);
}
```
### IScannerAnalyzerPlugin
Interface for language-specific scanners:
```csharp
namespace StellaOps.Scanner.Core.Plugins;
public interface IScannerAnalyzerPlugin : IAvailabilityPlugin
{
string AnalyzerId { get; }
string Language { get; }
IEnumerable<string> SupportedFilePatterns { get; }
Task<AnalysisResult> AnalyzeAsync(
AnalysisContext context,
CancellationToken cancellationToken);
}
```
## Creating a Plugin
### 1. Create Project
```bash
mkdir MyCompany.StellaOps.Plugin.MyPlugin
cd MyCompany.StellaOps.Plugin.MyPlugin
dotnet new classlib -f net10.0
```
### 2. Add Package References
```xml
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.1" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="path/to/StellaOps.Plugin/StellaOps.Plugin.csproj" />
</ItemGroup>
```
### 3. Create Options Class
```csharp
namespace MyCompany.StellaOps.Plugin.MyPlugin;
public sealed class MyPluginOptions
{
public string? ApiEndpoint { get; set; }
public string? ApiKey { get; set; }
public TimeSpan Timeout { get; set; } = TimeSpan.FromMinutes(1);
public int MaxRetries { get; set; } = 3;
}
```
### 4. Create Plugin Implementation
```csharp
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using StellaOps.Plugin;
namespace MyCompany.StellaOps.Plugin.MyPlugin;
public sealed class MyPlugin : IAvailabilityPlugin
{
public string PluginId => "mycompany.stellaops.myplugin";
public string DisplayName => "My Custom Plugin";
public bool IsAvailable(IServiceProvider services)
{
// Check if required dependencies are available
var config = services.GetService<IConfiguration>();
var apiKey = config?["MyPlugin:ApiKey"];
return !string.IsNullOrEmpty(apiKey);
}
public void Register(PluginRegistrationContext context)
{
var services = context.Services;
var configuration = context.Configuration;
// Bind configuration
var section = configuration.GetSection("MyPlugin");
services.Configure<MyPluginOptions>(options =>
{
section.Bind(options);
});
// Register services
services.AddSingleton<MyPluginService>();
}
}
```
### 5. Add Version Attribute
```csharp
using StellaOps.Plugin.Versioning;
[assembly: StellaPluginVersion("1.0.0", MinimumHostVersion = "1.0.0")]
```
## Registration Context
### Generic Context
```csharp
public sealed class PluginRegistrationContext
{
public IServiceCollection Services { get; }
public IConfiguration Configuration { get; }
}
```
### Router Transport Context
```csharp
public sealed class RouterTransportRegistrationContext
{
public IServiceCollection Services { get; }
public IConfiguration Configuration { get; }
public RouterTransportMode Mode { get; }
public string? ConfigurationSection { get; init; }
}
[Flags]
public enum RouterTransportMode
{
None = 0,
Server = 1,
Client = 2,
Both = Server | Client
}
```
## Configuration Binding
### Basic Binding
```csharp
public void Register(PluginRegistrationContext context)
{
context.Services.Configure<MyOptions>(options =>
{
context.Configuration.GetSection("MyPlugin").Bind(options);
});
}
```
### With Validation
```csharp
public void Register(PluginRegistrationContext context)
{
context.Services.AddOptions<MyOptions>()
.Bind(context.Configuration.GetSection("MyPlugin"))
.ValidateDataAnnotations()
.ValidateOnStart();
}
```
### Options Class with Validation
```csharp
using System.ComponentModel.DataAnnotations;
public sealed class MyOptions
{
[Required]
[Url]
public string? ApiEndpoint { get; set; }
[Required]
public string? ApiKey { get; set; }
[Range(1, 100)]
public int MaxRetries { get; set; } = 3;
}
```
## DI Registration Patterns
### Singleton Services
```csharp
public void Register(PluginRegistrationContext context)
{
context.Services.AddSingleton<IMyService, MyService>();
}
```
### Factory Registration
```csharp
public void Register(PluginRegistrationContext context)
{
context.Services.AddSingleton<IMyService>(sp =>
{
var options = sp.GetRequiredService<IOptions<MyOptions>>();
var logger = sp.GetRequiredService<ILogger<MyService>>();
return new MyService(options.Value, logger);
});
}
```
### Keyed Services
```csharp
public void Register(PluginRegistrationContext context)
{
context.Services.AddKeyedSingleton<IMyService>("myplugin", (sp, key) =>
{
return new MyService();
});
}
```
## Logging
### Structured Logging
```csharp
public sealed class MyPluginService
{
private readonly ILogger<MyPluginService> _logger;
public MyPluginService(ILogger<MyPluginService> logger)
{
_logger = logger;
}
public async Task ProcessAsync(string itemId)
{
using var scope = _logger.BeginScope(
new Dictionary<string, object>
{
["PluginId"] = "myplugin",
["ItemId"] = itemId
});
_logger.LogInformation("Processing item {ItemId}", itemId);
try
{
// Process...
_logger.LogDebug("Item {ItemId} processed successfully", itemId);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to process item {ItemId}", itemId);
throw;
}
}
}
```
## Error Handling
### Availability Check
```csharp
public bool IsAvailable(IServiceProvider services)
{
try
{
var config = services.GetRequiredService<IConfiguration>();
var options = config.GetSection("MyPlugin").Get<MyOptions>();
if (string.IsNullOrEmpty(options?.ApiEndpoint))
{
return false;
}
// Check connectivity if needed
return true;
}
catch (Exception ex)
{
_logger?.LogWarning(ex, "Availability check failed");
return false;
}
}
```
### Graceful Degradation
```csharp
public async Task<Result> ProcessAsync(Request request)
{
try
{
return await ProcessInternalAsync(request);
}
catch (ApiException ex) when (ex.StatusCode == 429)
{
_logger.LogWarning("Rate limited, using cached data");
return await GetCachedResultAsync(request);
}
catch (TimeoutException)
{
_logger.LogWarning("Request timeout, using fallback");
return Result.Fallback();
}
}
```
## Testing
### Unit Testing
```csharp
public class MyPluginTests
{
[Fact]
public void IsAvailable_WithValidConfig_ReturnsTrue()
{
// Arrange
var config = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string?>
{
["MyPlugin:ApiEndpoint"] = "https://api.example.com",
["MyPlugin:ApiKey"] = "test-key"
})
.Build();
var services = new ServiceCollection();
services.AddSingleton<IConfiguration>(config);
var provider = services.BuildServiceProvider();
var plugin = new MyPlugin();
// Act
var available = plugin.IsAvailable(provider);
// Assert
Assert.True(available);
}
[Fact]
public void Register_AddsServices()
{
// Arrange
var config = new ConfigurationBuilder().Build();
var services = new ServiceCollection();
var context = new PluginRegistrationContext(services, config);
var plugin = new MyPlugin();
// Act
plugin.Register(context);
// Assert
var provider = services.BuildServiceProvider();
Assert.NotNull(provider.GetService<MyPluginService>());
}
}
```
### Integration Testing
```csharp
public class MyPluginIntegrationTests : IClassFixture<PluginTestFixture>
{
private readonly PluginTestFixture _fixture;
public MyPluginIntegrationTests(PluginTestFixture fixture)
{
_fixture = fixture;
}
[Fact]
public async Task ProcessAsync_WithValidInput_ReturnsResult()
{
// Arrange
var service = _fixture.GetService<IMyService>();
var request = new Request { Id = "test-001" };
// Act
var result = await service.ProcessAsync(request);
// Assert
Assert.NotNull(result);
Assert.Equal("test-001", result.RequestId);
}
}
```
## Packaging
### Project Configuration
```xml
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<!-- Package metadata -->
<PackageId>MyCompany.StellaOps.Plugin.MyPlugin</PackageId>
<Version>1.0.0</Version>
<Authors>My Company</Authors>
<Description>My custom StellaOps plugin</Description>
<!-- Plugin output -->
<IsPackable>true</IsPackable>
<IncludeBuildOutput>true</IncludeBuildOutput>
</PropertyGroup>
```
### Plugin Manifest
Create `plugin.json`:
```json
{
"schemaVersion": "2.0",
"id": "mycompany.stellaops.myplugin",
"name": "My Custom Plugin",
"version": "1.0.0",
"assembly": {
"path": "MyCompany.StellaOps.Plugin.MyPlugin.dll",
"entryType": "MyCompany.StellaOps.Plugin.MyPlugin.MyPlugin"
},
"capabilities": ["custom-feature"],
"platforms": ["linux-x64", "win-x64", "osx-arm64"],
"enabled": true,
"priority": 100
}
```
### Build and Package
```bash
# Build release
dotnet build -c Release
# Create NuGet package
dotnet pack -c Release
# Publish to plugins directory
dotnet publish -c Release -o ./plugins
```
## Signing
### Sign with Cosign
```bash
# Generate key pair
cosign generate-key-pair
# Sign plugin assembly
cosign sign-blob --key cosign.key \
plugins/MyPlugin.dll \
--output-file plugins/MyPlugin.dll.sig
```
### Verify Signature
```bash
cosign verify-blob --key cosign.pub \
--signature plugins/MyPlugin.dll.sig \
plugins/MyPlugin.dll
```
## Best Practices
### Determinism
- Use stable ordering for collections
- Use UTC timestamps in ISO-8601 format
- Avoid non-deterministic random values
- Cache results consistently
### Offline Support
- Don't hardcode external URLs
- Provide fallback mechanisms
- Cache remote data when possible
- Log warnings, not errors, for optional network features
### Performance
- Use async/await properly
- Implement cancellation tokens
- Pool expensive resources
- Limit memory allocations
### Security
- Validate all inputs
- Sanitize output data
- Use secure defaults
- Never log secrets
## Examples
See the [plugin templates](./plugin-templates/README.md) for complete working examples.
## See Also
- [Plugin Overview](../plugins/README.md)
- [Plugin Architecture](../plugins/ARCHITECTURE.md)
- [Plugin Configuration](../plugins/CONFIGURATION.md)
- [Router Transport Development](../router/transports/development.md)