docs consolidation and others
This commit is contained in:
16
docs/dev/sdks/go.md
Normal file
16
docs/dev/sdks/go.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# SDK Guide: Go (outline)
|
||||
|
||||
## Pending Inputs
|
||||
- See sprint SPRINT_0309_0001_0009_docs_tasks_md_ix action tracker; inputs due 2025-12-09..12 from owning guilds.
|
||||
|
||||
## Determinism Checklist
|
||||
- [ ] Hash any inbound assets/payloads; place sums alongside artifacts (e.g., SHA256SUMS in this folder).
|
||||
- [ ] Keep examples offline-friendly and deterministic (fixed seeds, pinned versions, stable ordering).
|
||||
- [ ] Note source/approver for any provided captures or schemas.
|
||||
|
||||
## Sections to fill (once inputs arrive)
|
||||
- Module import and version pinning (`go.mod`).
|
||||
- Auth helpers and tenancy headers.
|
||||
- Context-first patterns, retries, telemetry defaults.
|
||||
- Streaming and large payload handling.
|
||||
- Examples (CLI + library) with hash-listed fixtures.
|
||||
16
docs/dev/sdks/java.md
Normal file
16
docs/dev/sdks/java.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# SDK Guide: Java (outline)
|
||||
|
||||
## Pending Inputs
|
||||
- See sprint SPRINT_0309_0001_0009_docs_tasks_md_ix action tracker; inputs due 2025-12-09..12 from owning guilds.
|
||||
|
||||
## Determinism Checklist
|
||||
- [ ] Hash any inbound assets/payloads; place sums alongside artifacts (e.g., SHA256SUMS in this folder).
|
||||
- [ ] Keep examples offline-friendly and deterministic (fixed seeds, pinned versions, stable ordering).
|
||||
- [ ] Note source/approver for any provided captures or schemas.
|
||||
|
||||
## Sections to fill (once inputs arrive)
|
||||
- Build tooling and version pinning (Maven/Gradle) with checksums.
|
||||
- Auth helpers and tenancy headers.
|
||||
- Retry/pagination/telemetry defaults.
|
||||
- Streaming/upload helpers.
|
||||
- Example usage with hash-listed fixtures.
|
||||
56
docs/dev/sdks/overview.md
Normal file
56
docs/dev/sdks/overview.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# SDKs Overview
|
||||
|
||||
StellaOps provides SDKs for extending platform functionality and integrating with external systems.
|
||||
|
||||
## Plugin SDK
|
||||
|
||||
The Plugin SDK enables development of custom plugins for all StellaOps modules:
|
||||
|
||||
| SDK | Purpose | Documentation |
|
||||
|-----|---------|---------------|
|
||||
| **Plugin Development** | Create custom plugins | [Plugin Development Guide](./plugin-development.md) |
|
||||
| **Plugin Templates** | `dotnet new` templates | [Plugin Templates](./plugin-templates/README.md) |
|
||||
|
||||
### Plugin Categories
|
||||
|
||||
| Category | Interface | Use Case |
|
||||
|----------|-----------|----------|
|
||||
| Router Transports | `IRouterTransportPlugin` | Custom communication protocols |
|
||||
| Authority Providers | `IAuthorityPlugin` | Identity/auth providers |
|
||||
| Concelier Connectors | `IConcielierConnector` | Vulnerability data sources |
|
||||
| Scanner Analyzers | `IScannerAnalyzerPlugin` | Language-specific scanners |
|
||||
| Crypto Providers | `ICryptoPlugin` | Cryptographic implementations |
|
||||
| Notify Channels | `INotifyChannel` | Notification delivery |
|
||||
|
||||
## Language SDKs
|
||||
|
||||
Client SDKs for integrating with StellaOps APIs:
|
||||
|
||||
| Language | Status | Documentation |
|
||||
|----------|--------|---------------|
|
||||
| [Go](./go.md) | Planned | - |
|
||||
| [Java](./java.md) | Planned | - |
|
||||
| [Python](./python.md) | Planned | - |
|
||||
| [TypeScript](./typescript.md) | Planned | - |
|
||||
|
||||
## Determinism Checklist
|
||||
|
||||
All SDKs and plugins must maintain determinism:
|
||||
|
||||
- [ ] Hash any inbound assets/payloads; place sums alongside artifacts
|
||||
- [ ] Keep examples offline-friendly and deterministic (fixed seeds, pinned versions, stable ordering)
|
||||
- [ ] Note source/approver for any provided captures or schemas
|
||||
|
||||
## SDK Design Principles
|
||||
|
||||
- **Offline-first**: No hidden network calls; explicit configuration for remote access
|
||||
- **Deterministic**: Stable ordering, reproducible outputs
|
||||
- **Typed**: Strong typing for compile-time safety
|
||||
- **Async-native**: First-class async/await support
|
||||
- **Testable**: Easy to mock and test in isolation
|
||||
|
||||
## See Also
|
||||
|
||||
- [Plugin Architecture](../plugins/ARCHITECTURE.md)
|
||||
- [Plugin Configuration](../plugins/CONFIGURATION.md)
|
||||
- [Plugin SDK Guide](../PLUGIN_SDK_GUIDE.md)
|
||||
580
docs/dev/sdks/plugin-development.md
Normal file
580
docs/dev/sdks/plugin-development.md
Normal 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)
|
||||
103
docs/dev/sdks/plugin-templates/README.md
Normal file
103
docs/dev/sdks/plugin-templates/README.md
Normal file
@@ -0,0 +1,103 @@
|
||||
# StellaOps Plugin Templates
|
||||
|
||||
This directory contains `dotnet new` templates for creating StellaOps plugins.
|
||||
|
||||
## Available Templates
|
||||
|
||||
| Template | Short Name | Description |
|
||||
|----------|------------|-------------|
|
||||
| StellaOps Connector Plugin | `stellaops-plugin-connector` | Create a data connector plugin (e.g., VEX, advisory feeds) |
|
||||
| StellaOps Scheduled Job Plugin | `stellaops-plugin-scheduler` | Create a scheduled job plugin |
|
||||
|
||||
## Installation
|
||||
|
||||
### From Local Directory
|
||||
|
||||
```bash
|
||||
dotnet new install ./templates
|
||||
```
|
||||
|
||||
### From NuGet Package
|
||||
|
||||
```bash
|
||||
dotnet new install StellaOps.Templates
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Create a Connector Plugin
|
||||
|
||||
```bash
|
||||
# Create with defaults
|
||||
dotnet new stellaops-plugin-connector -n MyCompany.AcmeConnector
|
||||
|
||||
# Create with custom parameters
|
||||
dotnet new stellaops-plugin-connector \
|
||||
-n MyCompany.AcmeConnector \
|
||||
--connectorName Acme \
|
||||
--connectorId acme-vex \
|
||||
--namespace MyCompany.Plugins.Acme
|
||||
```
|
||||
|
||||
### Create a Scheduled Job Plugin
|
||||
|
||||
```bash
|
||||
# Create with defaults
|
||||
dotnet new stellaops-plugin-scheduler -n MyCompany.CleanupJob
|
||||
|
||||
# Create with custom parameters
|
||||
dotnet new stellaops-plugin-scheduler \
|
||||
-n MyCompany.CleanupJob \
|
||||
--jobName Cleanup \
|
||||
--cronSchedule "0 */6 * * *" \
|
||||
--namespace MyCompany.Plugins.Cleanup
|
||||
```
|
||||
|
||||
## Template Parameters
|
||||
|
||||
### Connector Plugin Parameters
|
||||
|
||||
| Parameter | Description | Default |
|
||||
|-----------|-------------|---------|
|
||||
| `--connectorName` | Name of the connector | MyConnector |
|
||||
| `--connectorId` | Unique connector identifier | my-connector |
|
||||
| `--namespace` | Root namespace | StellaOps.Plugin.MyConnector |
|
||||
| `--pluginVersion` | Initial version | 1.0.0 |
|
||||
|
||||
### Scheduler Plugin Parameters
|
||||
|
||||
| Parameter | Description | Default |
|
||||
|-----------|-------------|---------|
|
||||
| `--jobName` | Name of the scheduled job | MyJob |
|
||||
| `--cronSchedule` | Default cron schedule | 0 0 * * * |
|
||||
| `--namespace` | Root namespace | StellaOps.Plugin.MyJob |
|
||||
| `--pluginVersion` | Initial version | 1.0.0 |
|
||||
|
||||
## Building the Template Pack
|
||||
|
||||
```bash
|
||||
cd templates
|
||||
dotnet pack -c Release
|
||||
```
|
||||
|
||||
This creates `StellaOps.Templates.<version>.nupkg` that can be published to NuGet.
|
||||
|
||||
## Uninstalling Templates
|
||||
|
||||
```bash
|
||||
dotnet new uninstall StellaOps.Templates
|
||||
```
|
||||
|
||||
## Plugin Development Guide
|
||||
|
||||
After creating a plugin from a template:
|
||||
|
||||
1. **Update options**: Modify the `*Options.cs` file with your configuration
|
||||
2. **Implement logic**: Add your business logic to the main class
|
||||
3. **Add validation**: Update the validator for your specific requirements
|
||||
4. **Add tests**: Create a test project for your plugin
|
||||
5. **Build**: `dotnet build -c Release`
|
||||
6. **Sign**: `cosign sign --key $COSIGN_KEY <assembly.dll>`
|
||||
7. **Deploy**: Copy to the plugin binaries directory
|
||||
|
||||
For more details, see `docs/10_PLUGIN_SDK_GUIDE.md`.
|
||||
28
docs/dev/sdks/plugin-templates/StellaOps.Templates.csproj
Normal file
28
docs/dev/sdks/plugin-templates/StellaOps.Templates.csproj
Normal file
@@ -0,0 +1,28 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<PackageType>Template</PackageType>
|
||||
<PackageVersion>1.0.0</PackageVersion>
|
||||
<PackageId>StellaOps.Templates</PackageId>
|
||||
<Title>StellaOps Plugin Templates</Title>
|
||||
<Authors>StellaOps</Authors>
|
||||
<Description>Templates for creating StellaOps plugins including connectors and scheduled jobs.</Description>
|
||||
<PackageTags>dotnet-new;templates;stellaops;plugin</PackageTags>
|
||||
<PackageLicenseExpression>AGPL-3.0-or-later</PackageLicenseExpression>
|
||||
<PackageProjectUrl>https://stellaops.io</PackageProjectUrl>
|
||||
<RepositoryUrl>https://git.stella-ops.org/stella-ops.org/git.stella-ops.org</RepositoryUrl>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<IncludeContentInPack>true</IncludeContentInPack>
|
||||
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||
<ContentTargetFolders>content</ContentTargetFolders>
|
||||
<NoWarn>$(NoWarn);NU5128</NoWarn>
|
||||
<NoDefaultExcludes>true</NoDefaultExcludes>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="stellaops-plugin-connector\**\*" Exclude="stellaops-plugin-connector\**\bin\**;stellaops-plugin-connector\**\obj\**" />
|
||||
<Content Include="stellaops-plugin-scheduler\**\*" Exclude="stellaops-plugin-scheduler\**\bin\**;stellaops-plugin-scheduler\**\obj\**" />
|
||||
<Compile Remove="**\*" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/template",
|
||||
"author": "StellaOps",
|
||||
"classifications": ["StellaOps", "Plugin", "Connector"],
|
||||
"identity": "StellaOps.Plugin.Connector",
|
||||
"name": "StellaOps Connector Plugin",
|
||||
"shortName": "stellaops-plugin-connector",
|
||||
"description": "A template for creating StellaOps connector plugins (e.g., VEX, advisory, or feed connectors)",
|
||||
"tags": {
|
||||
"language": "C#",
|
||||
"type": "project"
|
||||
},
|
||||
"sourceName": "MyConnector",
|
||||
"preferNameDirectory": true,
|
||||
"symbols": {
|
||||
"connectorName": {
|
||||
"type": "parameter",
|
||||
"datatype": "text",
|
||||
"description": "The name of the connector (e.g., 'Acme' for AcmeConnector)",
|
||||
"defaultValue": "MyConnector",
|
||||
"replaces": "MyConnector",
|
||||
"fileRename": "MyConnector"
|
||||
},
|
||||
"connectorId": {
|
||||
"type": "parameter",
|
||||
"datatype": "text",
|
||||
"description": "The unique connector identifier (e.g., 'acme-vex')",
|
||||
"defaultValue": "my-connector",
|
||||
"replaces": "my-connector"
|
||||
},
|
||||
"namespace": {
|
||||
"type": "parameter",
|
||||
"datatype": "text",
|
||||
"description": "The root namespace for the plugin",
|
||||
"defaultValue": "StellaOps.Plugin.MyConnector",
|
||||
"replaces": "StellaOps.Plugin.MyConnector"
|
||||
},
|
||||
"pluginVersion": {
|
||||
"type": "parameter",
|
||||
"datatype": "text",
|
||||
"description": "The initial plugin version",
|
||||
"defaultValue": "1.0.0",
|
||||
"replaces": "1.0.0-template"
|
||||
}
|
||||
},
|
||||
"postActions": [
|
||||
{
|
||||
"description": "Restore NuGet packages required by this project",
|
||||
"manualInstructions": [{ "text": "Run 'dotnet restore'" }],
|
||||
"actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025",
|
||||
"continueOnError": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.DependencyInjection;
|
||||
using StellaOps.DependencyInjection.Validation;
|
||||
|
||||
namespace StellaOps.Plugin.MyConnector;
|
||||
|
||||
/// <summary>
|
||||
/// Registers MyConnector services with the dependency injection container.
|
||||
/// </summary>
|
||||
public sealed class MyConnectorDependencyInjectionRoutine : IDependencyInjectionRoutine
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IServiceCollection Register(IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
// Register options with fail-fast validation
|
||||
services.AddOptionsWithValidation<MyConnectorOptions, MyConnectorOptionsValidator>(
|
||||
MyConnectorOptions.SectionName);
|
||||
|
||||
// Register the connector plugin
|
||||
services.AddSingleton<IConnectorPlugin, MyConnectorPlugin>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Plugin;
|
||||
|
||||
namespace StellaOps.Plugin.MyConnector;
|
||||
|
||||
/// <summary>
|
||||
/// Connector implementation for MyConnector.
|
||||
/// </summary>
|
||||
public sealed class MyConnector : IFeedConnector
|
||||
{
|
||||
private readonly ILogger<MyConnector> _logger;
|
||||
private readonly MyConnectorOptions _options;
|
||||
|
||||
public MyConnector(ILogger<MyConnector> logger, MyConnectorOptions options)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique identifier for this connector.
|
||||
/// </summary>
|
||||
public string Id => "my-connector";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the display name for this connector.
|
||||
/// </summary>
|
||||
public string DisplayName => "My Connector";
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<FetchResult> FetchAsync(FetchContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_logger.LogInformation("Fetching data from {ConnectorId}...", Id);
|
||||
|
||||
// TODO: Implement your fetch logic here
|
||||
// Example: Download data from an external source
|
||||
await Task.Delay(100, cancellationToken); // Placeholder
|
||||
|
||||
return new FetchResult(
|
||||
Success: true,
|
||||
Data: Array.Empty<byte>(),
|
||||
ContentType: "application/json",
|
||||
ETag: null);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<ParseResult> ParseAsync(byte[] data, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_logger.LogInformation("Parsing data from {ConnectorId}...", Id);
|
||||
|
||||
// TODO: Implement your parsing logic here
|
||||
await Task.Yield();
|
||||
|
||||
return new ParseResult(
|
||||
Success: true,
|
||||
Items: Array.Empty<object>(),
|
||||
Errors: Array.Empty<string>());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<MapResult> MapAsync(object item, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_logger.LogInformation("Mapping item from {ConnectorId}...", Id);
|
||||
|
||||
// TODO: Implement your mapping logic here
|
||||
await Task.Yield();
|
||||
|
||||
return new MapResult(
|
||||
Success: true,
|
||||
MappedItem: item,
|
||||
Errors: Array.Empty<string>());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of a fetch operation.
|
||||
/// </summary>
|
||||
public sealed record FetchResult(
|
||||
bool Success,
|
||||
byte[] Data,
|
||||
string? ContentType,
|
||||
string? ETag);
|
||||
|
||||
/// <summary>
|
||||
/// Result of a parse operation.
|
||||
/// </summary>
|
||||
public sealed record ParseResult(
|
||||
bool Success,
|
||||
IReadOnlyList<object> Items,
|
||||
IReadOnlyList<string> Errors);
|
||||
|
||||
/// <summary>
|
||||
/// Result of a map operation.
|
||||
/// </summary>
|
||||
public sealed record MapResult(
|
||||
bool Success,
|
||||
object? MappedItem,
|
||||
IReadOnlyList<string> Errors);
|
||||
|
||||
/// <summary>
|
||||
/// Context for fetch operations.
|
||||
/// </summary>
|
||||
public sealed record FetchContext(
|
||||
string? LastETag = null,
|
||||
DateTimeOffset? LastFetchTime = null);
|
||||
@@ -0,0 +1,42 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace StellaOps.Plugin.MyConnector;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration options for the MyConnector plugin.
|
||||
/// </summary>
|
||||
public sealed class MyConnectorOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// The configuration section name.
|
||||
/// </summary>
|
||||
public const string SectionName = "Plugins:MyConnector";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the base URL for the connector's data source.
|
||||
/// </summary>
|
||||
[Required]
|
||||
public required string BaseUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the API key for authentication.
|
||||
/// </summary>
|
||||
public string? ApiKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the request timeout in seconds. Default is 30.
|
||||
/// </summary>
|
||||
[Range(1, 300)]
|
||||
public int TimeoutSeconds { get; set; } = 30;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum number of retry attempts. Default is 3.
|
||||
/// </summary>
|
||||
[Range(0, 10)]
|
||||
public int MaxRetries { get; set; } = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether to enable verbose logging.
|
||||
/// </summary>
|
||||
public bool EnableVerboseLogging { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.DependencyInjection.Validation;
|
||||
|
||||
namespace StellaOps.Plugin.MyConnector;
|
||||
|
||||
/// <summary>
|
||||
/// Validates <see cref="MyConnectorOptions"/> configuration.
|
||||
/// </summary>
|
||||
public sealed class MyConnectorOptionsValidator : OptionsValidatorBase<MyConnectorOptions>
|
||||
{
|
||||
protected override string SectionPrefix => MyConnectorOptions.SectionName;
|
||||
|
||||
protected override void ValidateOptions(MyConnectorOptions options, ValidationContext context)
|
||||
{
|
||||
context
|
||||
.RequireNotEmpty(options.BaseUrl, nameof(options.BaseUrl))
|
||||
.RequirePositive(options.TimeoutSeconds, nameof(options.TimeoutSeconds))
|
||||
.RequireInRange(options.MaxRetries, nameof(options.MaxRetries), 0, 10);
|
||||
|
||||
// Validate URL format
|
||||
if (!string.IsNullOrWhiteSpace(options.BaseUrl) &&
|
||||
!Uri.TryCreate(options.BaseUrl, UriKind.Absolute, out _))
|
||||
{
|
||||
context.AddError(nameof(options.BaseUrl), "must be a valid absolute URL.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Plugin;
|
||||
using StellaOps.Plugin.Versioning;
|
||||
|
||||
// Declare plugin version for compatibility checking
|
||||
[assembly: StellaPluginVersion("1.0.0-template", MinimumHostVersion = "1.0.0")]
|
||||
|
||||
namespace StellaOps.Plugin.MyConnector;
|
||||
|
||||
/// <summary>
|
||||
/// Plugin entry point for the MyConnector connector.
|
||||
/// </summary>
|
||||
public sealed class MyConnectorPlugin : IConnectorPlugin
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "MyConnector";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsAvailable(IServiceProvider services)
|
||||
{
|
||||
// Add availability checks here (e.g., required configuration present)
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IFeedConnector Create(IServiceProvider services)
|
||||
{
|
||||
var logger = services.GetRequiredService<ILogger<MyConnector>>();
|
||||
var options = services.GetRequiredService<Microsoft.Extensions.Options.IOptions<MyConnectorOptions>>();
|
||||
return new MyConnector(logger, options.Value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
# MyConnector Plugin
|
||||
|
||||
A StellaOps connector plugin template.
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. Update the `MyConnectorOptions.cs` with your connector-specific configuration
|
||||
2. Implement the fetch, parse, and map logic in `MyConnector.cs`
|
||||
3. Update the plugin metadata in `MyConnectorPlugin.cs`
|
||||
4. Build and sign your plugin
|
||||
|
||||
## Configuration
|
||||
|
||||
Add the following to your `appsettings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"Plugins": {
|
||||
"MyConnector": {
|
||||
"BaseUrl": "https://api.example.com",
|
||||
"ApiKey": "your-api-key",
|
||||
"TimeoutSeconds": 30,
|
||||
"MaxRetries": 3
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
```bash
|
||||
dotnet build -c Release
|
||||
```
|
||||
|
||||
## Signing
|
||||
|
||||
For production use, sign your plugin with Cosign:
|
||||
|
||||
```bash
|
||||
cosign sign --key $COSIGN_KEY bin/Release/net10.0/StellaOps.Plugin.MyConnector.dll
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
dotnet test
|
||||
```
|
||||
@@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<Version>1.0.0-template</Version>
|
||||
<Description>StellaOps MyConnector Plugin</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Reference StellaOps plugin infrastructure -->
|
||||
<!-- Adjust paths based on your repository structure -->
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\__Libraries\StellaOps.Plugin\StellaOps.Plugin.csproj" />
|
||||
<ProjectReference Include="..\..\src\__Libraries\StellaOps.DependencyInjection\StellaOps.DependencyInjection.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/template",
|
||||
"author": "StellaOps",
|
||||
"classifications": ["StellaOps", "Plugin", "Scheduler", "Job"],
|
||||
"identity": "StellaOps.Plugin.Scheduler",
|
||||
"name": "StellaOps Scheduled Job Plugin",
|
||||
"shortName": "stellaops-plugin-scheduler",
|
||||
"description": "A template for creating StellaOps scheduled job plugins",
|
||||
"tags": {
|
||||
"language": "C#",
|
||||
"type": "project"
|
||||
},
|
||||
"sourceName": "MyJob",
|
||||
"preferNameDirectory": true,
|
||||
"symbols": {
|
||||
"jobName": {
|
||||
"type": "parameter",
|
||||
"datatype": "text",
|
||||
"description": "The name of the scheduled job (e.g., 'Cleanup' for CleanupJob)",
|
||||
"defaultValue": "MyJob",
|
||||
"replaces": "MyJob",
|
||||
"fileRename": "MyJob"
|
||||
},
|
||||
"cronSchedule": {
|
||||
"type": "parameter",
|
||||
"datatype": "text",
|
||||
"description": "Default cron schedule for the job",
|
||||
"defaultValue": "0 0 * * *",
|
||||
"replaces": "0 0 * * *"
|
||||
},
|
||||
"namespace": {
|
||||
"type": "parameter",
|
||||
"datatype": "text",
|
||||
"description": "The root namespace for the plugin",
|
||||
"defaultValue": "StellaOps.Plugin.MyJob",
|
||||
"replaces": "StellaOps.Plugin.MyJob"
|
||||
},
|
||||
"pluginVersion": {
|
||||
"type": "parameter",
|
||||
"datatype": "text",
|
||||
"description": "The initial plugin version",
|
||||
"defaultValue": "1.0.0",
|
||||
"replaces": "1.0.0-template"
|
||||
}
|
||||
},
|
||||
"postActions": [
|
||||
{
|
||||
"description": "Restore NuGet packages required by this project",
|
||||
"manualInstructions": [{ "text": "Run 'dotnet restore'" }],
|
||||
"actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025",
|
||||
"continueOnError": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using StellaOps.DependencyInjection;
|
||||
using StellaOps.DependencyInjection.Validation;
|
||||
|
||||
namespace StellaOps.Plugin.MyJob;
|
||||
|
||||
/// <summary>
|
||||
/// Registers MyJob services with the dependency injection container.
|
||||
/// </summary>
|
||||
public sealed class MyJobDependencyInjectionRoutine : IDependencyInjectionRoutine
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IServiceCollection Register(IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
// Register options with fail-fast validation
|
||||
services.AddOptionsWithValidation<MyJobOptions, MyJobOptionsValidator>(
|
||||
MyJobOptions.SectionName);
|
||||
|
||||
// Register the scheduled job
|
||||
services.AddSingleton<IScheduledJob, MyJob>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Plugin.Versioning;
|
||||
|
||||
// Declare plugin version for compatibility checking
|
||||
[assembly: StellaPluginVersion("1.0.0-template", MinimumHostVersion = "1.0.0")]
|
||||
|
||||
namespace StellaOps.Plugin.MyJob;
|
||||
|
||||
/// <summary>
|
||||
/// A scheduled job that runs on a configurable cron schedule.
|
||||
/// </summary>
|
||||
public sealed class MyJob : IScheduledJob
|
||||
{
|
||||
private readonly ILogger<MyJob> _logger;
|
||||
private readonly MyJobOptions _options;
|
||||
|
||||
public MyJob(ILogger<MyJob> logger, IOptions<MyJobOptions> options)
|
||||
{
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string JobId => "my-job";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string DisplayName => "My Scheduled Job";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string CronSchedule => _options.CronSchedule;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<JobResult> ExecuteAsync(JobContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_logger.LogInformation("Starting scheduled job {JobId} at {StartTime}", JobId, context.ScheduledTime);
|
||||
|
||||
try
|
||||
{
|
||||
// TODO: Implement your job logic here
|
||||
await Task.Delay(100, cancellationToken);
|
||||
|
||||
_logger.LogInformation("Completed scheduled job {JobId}", JobId);
|
||||
|
||||
return new JobResult(
|
||||
Success: true,
|
||||
Message: "Job completed successfully",
|
||||
ItemsProcessed: 0,
|
||||
Errors: Array.Empty<string>());
|
||||
}
|
||||
catch (Exception ex) when (ex is not OperationCanceledException)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to execute scheduled job {JobId}", JobId);
|
||||
|
||||
return new JobResult(
|
||||
Success: false,
|
||||
Message: ex.Message,
|
||||
ItemsProcessed: 0,
|
||||
Errors: new[] { ex.Message });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a scheduled job that can be executed by the scheduler.
|
||||
/// </summary>
|
||||
public interface IScheduledJob
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the unique identifier for this job.
|
||||
/// </summary>
|
||||
string JobId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the display name for this job.
|
||||
/// </summary>
|
||||
string DisplayName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the cron schedule expression for this job.
|
||||
/// </summary>
|
||||
string CronSchedule { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Executes the scheduled job.
|
||||
/// </summary>
|
||||
Task<JobResult> ExecuteAsync(JobContext context, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Context for job execution.
|
||||
/// </summary>
|
||||
public sealed record JobContext(
|
||||
DateTimeOffset ScheduledTime,
|
||||
DateTimeOffset ActualStartTime,
|
||||
string? CorrelationId = null);
|
||||
|
||||
/// <summary>
|
||||
/// Result of a job execution.
|
||||
/// </summary>
|
||||
public sealed record JobResult(
|
||||
bool Success,
|
||||
string? Message,
|
||||
int ItemsProcessed,
|
||||
IReadOnlyList<string> Errors);
|
||||
@@ -0,0 +1,43 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace StellaOps.Plugin.MyJob;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration options for the MyJob scheduled plugin.
|
||||
/// </summary>
|
||||
public sealed class MyJobOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// The configuration section name.
|
||||
/// </summary>
|
||||
public const string SectionName = "Plugins:MyJob";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the cron schedule expression.
|
||||
/// Default: "0 0 * * *" (daily at midnight).
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string CronSchedule { get; set; } = "0 0 * * *";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the job is enabled.
|
||||
/// </summary>
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum execution time in minutes.
|
||||
/// </summary>
|
||||
[Range(1, 1440)]
|
||||
public int MaxExecutionMinutes { get; set; } = 60;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the batch size for processing items.
|
||||
/// </summary>
|
||||
[Range(1, 10000)]
|
||||
public int BatchSize { get; set; } = 100;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether to continue processing on error.
|
||||
/// </summary>
|
||||
public bool ContinueOnError { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using StellaOps.DependencyInjection.Validation;
|
||||
|
||||
namespace StellaOps.Plugin.MyJob;
|
||||
|
||||
/// <summary>
|
||||
/// Validates <see cref="MyJobOptions"/> configuration.
|
||||
/// </summary>
|
||||
public sealed class MyJobOptionsValidator : OptionsValidatorBase<MyJobOptions>
|
||||
{
|
||||
protected override string SectionPrefix => MyJobOptions.SectionName;
|
||||
|
||||
protected override void ValidateOptions(MyJobOptions options, ValidationContext context)
|
||||
{
|
||||
context
|
||||
.RequireNotEmpty(options.CronSchedule, nameof(options.CronSchedule))
|
||||
.RequirePositive(options.MaxExecutionMinutes, nameof(options.MaxExecutionMinutes))
|
||||
.RequirePositive(options.BatchSize, nameof(options.BatchSize))
|
||||
.RequireInRange(options.MaxExecutionMinutes, nameof(options.MaxExecutionMinutes), 1, 1440)
|
||||
.RequireInRange(options.BatchSize, nameof(options.BatchSize), 1, 10000);
|
||||
|
||||
// Validate cron expression format (basic check)
|
||||
if (!string.IsNullOrWhiteSpace(options.CronSchedule))
|
||||
{
|
||||
var parts = options.CronSchedule.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length < 5 || parts.Length > 6)
|
||||
{
|
||||
context.AddError(nameof(options.CronSchedule), "must be a valid cron expression with 5 or 6 fields.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
# MyJob Scheduled Plugin
|
||||
|
||||
A StellaOps scheduled job plugin template.
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. Update the `MyJobOptions.cs` with your job-specific configuration
|
||||
2. Implement the job logic in `MyJob.cs`
|
||||
3. Build and sign your plugin
|
||||
|
||||
## Configuration
|
||||
|
||||
Add the following to your `appsettings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"Plugins": {
|
||||
"MyJob": {
|
||||
"Enabled": true,
|
||||
"CronSchedule": "0 0 * * *",
|
||||
"MaxExecutionMinutes": 60,
|
||||
"BatchSize": 100,
|
||||
"ContinueOnError": false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Cron Schedule Examples
|
||||
|
||||
- `0 0 * * *` - Daily at midnight
|
||||
- `0 */6 * * *` - Every 6 hours
|
||||
- `0 0 * * 0` - Weekly on Sunday at midnight
|
||||
- `0 0 1 * *` - Monthly on the 1st at midnight
|
||||
|
||||
## Building
|
||||
|
||||
```bash
|
||||
dotnet build -c Release
|
||||
```
|
||||
|
||||
## Signing
|
||||
|
||||
For production use, sign your plugin with Cosign:
|
||||
|
||||
```bash
|
||||
cosign sign --key $COSIGN_KEY bin/Release/net10.0/StellaOps.Plugin.MyJob.dll
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
dotnet test
|
||||
```
|
||||
@@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<Version>1.0.0-template</Version>
|
||||
<Description>StellaOps MyJob Scheduled Plugin</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Reference StellaOps plugin infrastructure -->
|
||||
<!-- Adjust paths based on your repository structure -->
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\__Libraries\StellaOps.Plugin\StellaOps.Plugin.csproj" />
|
||||
<ProjectReference Include="..\..\src\__Libraries\StellaOps.DependencyInjection\StellaOps.DependencyInjection.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
17
docs/dev/sdks/python.md
Normal file
17
docs/dev/sdks/python.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# SDK Guide: Python (outline)
|
||||
|
||||
## Pending Inputs
|
||||
- See sprint SPRINT_0309_0001_0009_docs_tasks_md_ix action tracker; inputs due 2025-12-09..12 from owning guilds.
|
||||
|
||||
## Determinism Checklist
|
||||
- [ ] Hash any inbound assets/payloads; place sums alongside artifacts (e.g., SHA256SUMS in this folder).
|
||||
- [ ] Keep examples offline-friendly and deterministic (fixed seeds, pinned versions, stable ordering).
|
||||
- [ ] Note source/approver for any provided captures or schemas.
|
||||
|
||||
## Sections to fill (once inputs arrive)
|
||||
- Installation & version pinning (pip/poetry/pipenv) with hashes.
|
||||
- Auth helpers (PAT/OAuth2) and tenancy headers.
|
||||
- Sync vs async clients usage.
|
||||
- Pagination, retries, telemetry defaults.
|
||||
- Streaming/upload helpers.
|
||||
- Example notebooks/scripts (hash-listed).
|
||||
17
docs/dev/sdks/typescript.md
Normal file
17
docs/dev/sdks/typescript.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# SDK Guide: TypeScript (outline)
|
||||
|
||||
## Pending Inputs
|
||||
- See sprint SPRINT_0309_0001_0009_docs_tasks_md_ix action tracker; inputs due 2025-12-09..12 from owning guilds.
|
||||
|
||||
## Determinism Checklist
|
||||
- [ ] Hash any inbound assets/payloads; place sums alongside artifacts (e.g., SHA256SUMS in this folder).
|
||||
- [ ] Keep examples offline-friendly and deterministic (fixed seeds, pinned versions, stable ordering).
|
||||
- [ ] Note source/approver for any provided captures or schemas.
|
||||
|
||||
## Sections to fill (once inputs arrive)
|
||||
- Installation & version pinning (npm/pnpm/yarn with lockfiles).
|
||||
- Auth helpers (PAT/OAuth2) and tenancy headers.
|
||||
- Pagination, retries, telemetry defaults.
|
||||
- Streaming/download helpers.
|
||||
- CI/offline bundle usage notes.
|
||||
- Example snippets (with hash-listed fixtures).
|
||||
Reference in New Issue
Block a user