Files
git.stella-ops.org/docs/10_PLUGIN_SDK_GUIDE.md
StellaOps Bot b058dbe031 up
2025-12-14 23:20:14 +02:00

429 lines
15 KiB
Markdown
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#10 · Plugin SDK Guide — **StellaOps**
*(v1.5  11Jul2025 · template install, no reload, IoC)*
---
##0Audience & Scope
Guidance for developers who extend StellaOps with schedule jobs, scanner adapters, TLS providers, notification channels, etc. Everything here is OSS; commercial variants simply ship additional signed plugins.
---
##1Prerequisites
| Tool | Min Version |
| ----------------------- | ----------------------------------------------------------------- |
| .NET SDK | {{ dotnet }} |
| **StellaOps templates** | install once via `bash dotnet new install StellaOps.Templates::*` |
| **Cosign** | 2.3+ — used to sign DLLs |
| xUnit | 2.6 |
| Docker CLI | only if your plugin shells out to containers |
---
##2Repository & Build Output
Every plugin is hosted in **`git.stellaops.org`**.
At publish time it must copy its signed artefacts to:
~~~text
src/backend/Stella.Ops.Plugin.Binaries/<MyPlugin>/
├── MyPlugin.dll
└── MyPlugin.dll.sig
~~~
The backend scans this folder on startup, verifies the **Cosign** signature, confirms the `[StellaPluginVersion]` gate, then loads the DLL inside an **isolated AssemblyLoadContext** to avoid dependency clashes
---
##3Project Scaffold
Generate with the installed template:
~~~bash
dotnet new stellaops-plugin-schedule \
-n MyPlugin.Schedule \
--output src
~~~
Result:
~~~text
src/
├─ MyPlugin.Schedule/
│ ├─ MyJob.cs
│ └─ MyPlugin.Schedule.csproj
└─ tests/
└─ MyPlugin.Schedule.Tests/
~~~
---
##4MSBuild Wiring
Add this to **`MyPlugin.Schedule.csproj`** so the signed DLL + `.sig` land in the canonical plugin folder:
~~~xml
<PropertyGroup>
<StellaPluginOut>$(SolutionDir)src/backend/Stella.Ops.Plugin.Binaries/$(MSBuildProjectName)</StellaPluginOut>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\StellaOps.Common\StellaOps.Common.csproj"
PrivateAssets="all" />
</ItemGroup>
<Target Name="CopyStellaPlugin" AfterTargets="Publish">
<MakeDir Directories="$(StellaPluginOut)" />
<Copy SourceFiles="$(PublishDir)$(AssemblyName).dll;$(PublishDir)$(AssemblyName).dll.sig"
DestinationFolder="$(StellaPluginOut)" />
</Target>
~~~
---
##5DependencyInjection Entrypoint
Backend autodiscovers restarttime bindings through two mechanisms:
1. **Service binding metadata** for simple contracts.
2. **`IDependencyInjectionRoutine`** implementations when you need full control.
###5.1Service binding metadata
Annotate implementations with `[ServiceBinding]` to declare their lifetime and service contract.
The loader honours scoped lifetimes and will register the service before executing any custom DI routines.
~~~csharp
using Microsoft.Extensions.DependencyInjection;
using StellaOps.DependencyInjection;
[ServiceBinding(typeof(IJob), ServiceLifetime.Scoped, RegisterAsSelf = true)]
public sealed class MyJob : IJob
{
// IJob dependencies can now use scoped services (Mongo sessions, etc.)
}
~~~
Use `RegisterAsSelf = true` when you also want to resolve the concrete type.
Set `ReplaceExisting = true` to override default descriptors if the host already provides one.
###5.2Dependency injection routines
For advanced scenarios continue to expose a routine:
~~~csharp
namespace StellaOps.DependencyInjection;
public sealed class IoCConfigurator : IDependencyInjectionRoutine
{
public IServiceCollection Register(IServiceCollection services, IConfiguration cfg)
{
services.AddSingleton<IJob, MyJob>(); // schedule job
services.Configure<MyPluginOptions>(cfg.GetSection("Plugins:MyPlugin"));
return services;
}
}
~~~
---
##6Schedule Plugins
###6.1 Minimal Job
~~~csharp
using StellaOps.Scheduling; // contract
[StellaPluginVersion("2.0.0")]
public sealed class MyJob : IJob
{
public async Task ExecuteAsync(CancellationToken ct)
{
Console.WriteLine("Hello from plugin!");
await Task.Delay(500, ct);
}
}
~~~
###6.2 Cron Registration
```csharp
services.AddCronJob<MyJob>("0 15 * * *"); // everyday
```
15:00
Cron syntax follows Hangfire rules 
##7Scanner Adapters
Implement IScannerRunner.
Register inside Configure:
```csharp
services.AddScanner<MyAltScanner>("alt"); // backend
```
selects by --engine alt
If the engine needs a sidecar container, include a Dockerfile in your repo and document resource expectations.
##8Packaging & Signing
```bash
dotnet publish -c Release -p:PublishSingleFile=true -o out
cosign sign --key $COSIGN_KEY out/MyPlugin.Schedule.dll # sign binary only
sha256sum out/MyPlugin.Schedule.dll > out/.sha256 # optional checksum
zip MyPlugin.zip out/* README.md
```
Unsigned DLLs are refused when StellaOps:Security:DisableUnsigned=false.
##9Deployment
```bash
docker cp MyPlugin.zip <backend>:/opt/plugins/ && docker restart <backend>
```
Check /health "plugins":["MyPlugin.Schedule@2.0.0"].
(Hotreload was removed to keep the core process simple and memorysafe.)
##10Configuration Patterns
| Need | Pattern |
| ------------ | --------------------------------------------------------- |
| Settings | Plugins:MyPlugin:* in appsettings.json. |
| Secrets | Redis secure:<plugin>:<key> (encrypted per TLS provider). |
| Dynamic cron | Implement ICronConfigurable; UI exposes editor. |
##11Testing & CI
| Layer | Tool | Gate |
| ----------- | -------------------------- | ------------------- |
| Unit | xUnit + Moq | ≥50% lines |
| Integration | Testcontainers run in CI | Job completes <5s |
| Style | dotnet | format 0 warnings |
Use the prebaked workflow in StellaOps.Templates as starting point.
##12Publishing to the Community Marketplace
Tag Git release pluginvX.Y.Z and attach the signed ZIP.
Submit a PR to stellaops/community-plugins.json with metadata & git URL.
On merge, the plugin shows up in the UI Marketplace.
##13Common Pitfalls
| Symptom | Root cause | Fix |
| ------------------- | -------------------------- | ------------------------------------------- |
| NotDetected | .sig missing | cosign sign |
| VersionGateMismatch | Backend 2.1 vs plugin 2.0 | Recompile / bump attribute |
| FileLoadException | Duplicate | StellaOps.Common Ensure PrivateAssets="all" |
| Redis | timeouts Large writes | Batch or use Mongo |
---
## 14 Plugin Version Compatibility (v2.0)
**IMPORTANT:** All plugins **must** declare a `[StellaPluginVersion]` attribute. Plugins without this attribute will be rejected by the host loader.
Declare your plugin's version and host compatibility requirements:
```csharp
using StellaOps.Plugin.Versioning;
// In AssemblyInfo.cs or any file at assembly level
[assembly: StellaPluginVersion("1.2.0", MinimumHostVersion = "1.0.0", MaximumHostVersion = "2.0.0")]
```
| Property | Purpose | Required |
|----------|---------|----------|
| `pluginVersion` (constructor) | Your plugin's semantic version | **Yes** |
| `MinimumHostVersion` | Lowest host version that can load this plugin | Recommended |
| `MaximumHostVersion` | Highest host version supported | Recommended for cross-major compatibility |
| `RequiresSignature` | Whether signature verification is mandatory (default: true) | No |
### Version Compatibility Rules
1. **Attribute Required:** Plugins without `[StellaPluginVersion]` are rejected
2. **Minimum Version:** Host version must be `MinimumHostVersion`
3. **Maximum Version:** Host version must be `MaximumHostVersion` (if specified)
4. **Strict Major Version:** If `MaximumHostVersion` is not specified, the plugin is assumed to only support the same major version as `MinimumHostVersion`
### Examples
```csharp
// Plugin works with host 1.0.0 through 2.x (explicit range)
[assembly: StellaPluginVersion("1.0.0", MinimumHostVersion = "1.0.0", MaximumHostVersion = "2.99.99")]
// Plugin works with host 2.x only (strict - no MaximumHostVersion means same major version)
[assembly: StellaPluginVersion("1.0.0", MinimumHostVersion = "2.0.0")]
// Plugin version 3.0.0 with no host constraints (uses plugin major version as reference)
[assembly: StellaPluginVersion("3.0.0")]
```
---
## 15 Plugin Host Configuration (v2.0)
Configure the plugin loader with security-first defaults in `PluginHostOptions`:
```csharp
var options = new PluginHostOptions
{
// Version enforcement (all default to true for security)
HostVersion = new Version(2, 0, 0),
EnforceVersionCompatibility = true, // Reject incompatible plugins
RequireVersionAttribute = true, // Reject plugins without [StellaPluginVersion]
StrictMajorVersionCheck = true, // Reject plugins crossing major version boundaries
// Signature verification (opt-in, requires infrastructure)
EnforceSignatureVerification = true,
SignatureVerifier = new CosignPluginVerifier(new CosignVerifierOptions
{
PublicKeyPath = "/keys/cosign.pub",
UseRekorTransparencyLog = true,
AllowUnsigned = false
})
};
var result = await PluginHost.LoadPluginsAsync(options, logger);
// Check for failures
if (result.HasFailures)
{
foreach (var failure in result.Failures)
{
logger.LogError("Plugin {Path} failed: {Reason} - {Message}",
failure.AssemblyPath, failure.Reason, failure.Message);
}
}
```
### Host Options Reference
| Option | Default | Purpose |
|--------|---------|---------|
| `HostVersion` | null | The host application version for compatibility checking |
| `EnforceVersionCompatibility` | **true** | Reject plugins that fail version checks |
| `RequireVersionAttribute` | **true** | Reject plugins without `[StellaPluginVersion]` |
| `StrictMajorVersionCheck` | **true** | Reject plugins that don't explicitly support the host's major version |
| `EnforceSignatureVerification` | false | Reject plugins without valid signatures |
| `SignatureVerifier` | null | The verifier implementation (e.g., `CosignPluginVerifier`) |
### Failure Reasons
| Reason | Description |
|--------|-------------|
| `LoadError` | Assembly could not be loaded (missing dependencies, corrupt file) |
| `SignatureInvalid` | Signature verification failed |
| `IncompatibleVersion` | Plugin version constraints not satisfied |
| `MissingVersionAttribute` | Plugin lacks required `[StellaPluginVersion]` attribute |
---
## 16 Fail-Fast Options Validation (v2.0)
Use the fail-fast validation pattern to catch configuration errors at startup:
```csharp
using StellaOps.DependencyInjection.Validation;
// Register options with automatic startup validation
services.AddOptionsWithValidation<MyPluginOptions, MyPluginOptionsValidator>(
MyPluginOptions.SectionName);
// Or with data annotations
services.AddOptionsWithDataAnnotations<MyPluginOptions>(
MyPluginOptions.SectionName);
```
Create validators using the base class:
```csharp
public sealed class MyPluginOptionsValidator : OptionsValidatorBase<MyPluginOptions>
{
protected override string SectionPrefix => "Plugins:MyPlugin";
protected override void ValidateOptions(MyPluginOptions options, ValidationContext context)
{
context
.RequireNotEmpty(options.BaseUrl, nameof(options.BaseUrl))
.RequirePositive(options.TimeoutSeconds, nameof(options.TimeoutSeconds))
.RequireInRange(options.MaxRetries, nameof(options.MaxRetries), 0, 10);
}
}
```
---
## 17 Available Templates (v2.0)
Install and use the official plugin templates:
```bash
# Install from local templates directory
dotnet new install ./templates
# Or install from NuGet
dotnet new install StellaOps.Templates
# Create a connector plugin
dotnet new stellaops-plugin-connector -n MyCompany.AcmeConnector
# Create a scheduled job plugin
dotnet new stellaops-plugin-scheduler -n MyCompany.CleanupJob
```
Templates include:
- Plugin entry point with version attribute
- Options class with data annotations
- Options validator with fail-fast pattern
- DI routine registration
- README with build/sign instructions
---
## 18 Migration Guide: v2.0 to v2.1
### Breaking Change: Version Attribute Required
As of v2.1, all plugins **must** include a `[StellaPluginVersion]` attribute. Plugins without this attribute will be rejected with `MissingVersionAttribute` failure.
**Before (v2.0):** Optional, plugins without attribute loaded with warning.
**After (v2.1):** Required, plugins without attribute are rejected.
### Migration Steps
1. Add the version attribute to your plugin's AssemblyInfo.cs:
```csharp
[assembly: StellaPluginVersion("1.0.0", MinimumHostVersion = "2.0.0", MaximumHostVersion = "2.99.99")]
```
2. If your plugin must support multiple major host versions, explicitly set `MaximumHostVersion`:
```csharp
// Supports host 1.x through 3.x
[assembly: StellaPluginVersion("1.0.0", MinimumHostVersion = "1.0.0", MaximumHostVersion = "3.99.99")]
```
3. Rebuild and re-sign your plugin.
### Opt-out (Not Recommended)
If you must load legacy plugins without version attributes:
```csharp
var options = new PluginHostOptions
{
RequireVersionAttribute = false, // Allow unversioned plugins (NOT recommended)
StrictMajorVersionCheck = false // Allow cross-major version loading
};
```
---
## Change Log
| Version | Date | Changes |
|---------|------|---------|
| v2.1 | 2025-12-14 | **Breaking:** `[StellaPluginVersion]` attribute now required by default. Added `RequireVersionAttribute`, `StrictMajorVersionCheck` options. Added `MissingVersionAttribute` failure reason. |
| v2.0 | 2025-12-14 | Added StellaPluginVersion attribute, Cosign verification options, fail-fast validation, new templates |
| v1.5 | 2025-07-11 | Template install, no hot-reload, IoC conventions |