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

15 KiB
Executable File
Raw Blame History

#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:

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:

dotnet new stellaops-plugin-schedule \
       -n MyPlugin.Schedule \
       --output src

Result:

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:

<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.

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:

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

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

services.AddCronJob<MyJob>("0 15 * * *"); // everyday 

15:00 Cron syntax follows Hangfire rules 

##7Scanner Adapters

Implement IScannerRunner. Register inside Configure:

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

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

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:: (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:

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

// 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:

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:

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:

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:

# 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:

    [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:

    // 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.

If you must load legacy plugins without version attributes:

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