release orchestrator v1 draft and build fixes

This commit is contained in:
master
2026-01-12 12:24:17 +02:00
parent f3de858c59
commit 9873f80830
1598 changed files with 240385 additions and 5944 deletions

View File

@@ -0,0 +1,347 @@
namespace StellaOps.AdvisoryAI.Scm.Plugin.Unified;
using StellaOps.AdvisoryAI.Remediation.ScmConnector;
using StellaOps.Plugin.Abstractions;
using StellaOps.Plugin.Abstractions.Capabilities;
using StellaOps.Plugin.Abstractions.Context;
using StellaOps.Plugin.Abstractions.Health;
using StellaOps.Plugin.Abstractions.Lifecycle;
// Type aliases to disambiguate between AdvisoryAI and Plugin.Abstractions types
using AdvisoryBranchResult = StellaOps.AdvisoryAI.Remediation.ScmConnector.BranchResult;
using AdvisoryFileUpdateResult = StellaOps.AdvisoryAI.Remediation.ScmConnector.FileUpdateResult;
using AdvisoryPrCreateResult = StellaOps.AdvisoryAI.Remediation.ScmConnector.PrCreateResult;
using AdvisoryPrStatusResult = StellaOps.AdvisoryAI.Remediation.ScmConnector.PrStatusResult;
using AdvisoryCiStatusResult = StellaOps.AdvisoryAI.Remediation.ScmConnector.CiStatusResult;
using AdvisoryCiCheck = StellaOps.AdvisoryAI.Remediation.ScmConnector.CiCheck;
/// <summary>
/// Adapts an existing IScmConnector to the unified IPlugin and IScmCapability interfaces.
/// This enables gradual migration of AdvisoryAI SCM connectors to the unified plugin architecture.
/// </summary>
/// <remarks>
/// The AdvisoryAI IScmConnector focuses on PR/write operations while IScmCapability focuses on
/// read operations. This adapter bridges both, implementing what it can from the underlying connector.
/// </remarks>
public sealed class ScmPluginAdapter : IPlugin, IScmCapability
{
private readonly IScmConnector _inner;
private readonly IScmConnectorPlugin _plugin;
private readonly ScmConnectorOptions _options;
private IPluginContext? _context;
private PluginLifecycleState _state = PluginLifecycleState.Discovered;
/// <summary>
/// Creates a new adapter for an existing SCM connector.
/// </summary>
/// <param name="inner">The existing SCM connector to wrap.</param>
/// <param name="plugin">The plugin metadata for this connector.</param>
/// <param name="options">Connector configuration options.</param>
public ScmPluginAdapter(IScmConnector inner, IScmConnectorPlugin plugin, ScmConnectorOptions options)
{
_inner = inner ?? throw new ArgumentNullException(nameof(inner));
_plugin = plugin ?? throw new ArgumentNullException(nameof(plugin));
_options = options ?? throw new ArgumentNullException(nameof(options));
}
/// <inheritdoc />
public PluginInfo Info => new(
Id: $"com.stellaops.scm.{_inner.ScmType}",
Name: $"{_plugin.DisplayName} SCM Connector",
Version: "1.0.0",
Vendor: "Stella Ops",
Description: $"{_plugin.DisplayName} source control management connector");
/// <inheritdoc />
public PluginTrustLevel TrustLevel => PluginTrustLevel.BuiltIn;
/// <inheritdoc />
public PluginCapabilities Capabilities => PluginCapabilities.Scm | PluginCapabilities.Network;
/// <inheritdoc />
public PluginLifecycleState State => _state;
#region IScmCapability - Core properties
/// <inheritdoc />
public string ConnectorType => $"scm.{_inner.ScmType}";
/// <inheritdoc />
public string DisplayName => _plugin.DisplayName;
/// <inheritdoc />
public string ScmType => _inner.ScmType;
#endregion
#region IScmCapability - Connection
/// <inheritdoc />
public bool CanHandle(string repositoryUrl)
{
return _plugin.CanHandle(repositoryUrl);
}
/// <inheritdoc />
public async Task<ConnectionTestResult> TestConnectionAsync(CancellationToken ct)
{
// Try to perform any operation to test connectivity
// Since IScmConnector doesn't have a dedicated test method, we assume success
// if the connector was created successfully
await Task.Delay(0, ct);
return ConnectionTestResult.Succeeded(TimeSpan.FromMilliseconds(1));
}
/// <inheritdoc />
public async Task<ConnectionInfo> GetConnectionInfoAsync(CancellationToken ct)
{
await Task.Delay(0, ct);
return new ConnectionInfo(
EndpointUrl: _options.BaseUrl ?? $"https://api.{_inner.ScmType}.com",
AuthenticatedAs: "configured-token",
Metadata: new Dictionary<string, object>
{
["scmType"] = _inner.ScmType,
["hasToken"] = !string.IsNullOrEmpty(_options.ApiToken)
});
}
#endregion
#region IScmCapability - Read operations (limited support via IScmConnector)
/// <inheritdoc />
public Task<IReadOnlyList<ScmBranch>> ListBranchesAsync(string repositoryUrl, CancellationToken ct)
{
// IScmConnector doesn't support listing branches
// This would require extending the adapter or using direct API calls
throw new NotSupportedException(
"Branch listing not supported via IScmConnector adapter. " +
"Use the IScmCapability-native implementation or extend this adapter.");
}
/// <inheritdoc />
public Task<IReadOnlyList<ScmCommit>> ListCommitsAsync(
string repositoryUrl,
string branch,
int limit = 50,
CancellationToken ct = default)
{
throw new NotSupportedException(
"Commit listing not supported via IScmConnector adapter.");
}
/// <inheritdoc />
public Task<ScmCommit> GetCommitAsync(string repositoryUrl, string commitSha, CancellationToken ct)
{
throw new NotSupportedException(
"Commit retrieval not supported via IScmConnector adapter.");
}
/// <inheritdoc />
public Task<ScmFileContent> GetFileAsync(
string repositoryUrl,
string filePath,
string? reference = null,
CancellationToken ct = default)
{
throw new NotSupportedException(
"File retrieval not supported via IScmConnector adapter.");
}
/// <inheritdoc />
public Task<Stream> GetArchiveAsync(
string repositoryUrl,
string reference,
ArchiveFormat format = ArchiveFormat.TarGz,
CancellationToken ct = default)
{
throw new NotSupportedException(
"Archive download not supported via IScmConnector adapter.");
}
/// <inheritdoc />
public Task<ScmWebhook> UpsertWebhookAsync(
string repositoryUrl,
ScmWebhookConfig config,
CancellationToken ct)
{
throw new NotSupportedException(
"Webhook management not supported via IScmConnector adapter.");
}
/// <inheritdoc />
public Task<ScmUser> GetCurrentUserAsync(CancellationToken ct)
{
throw new NotSupportedException(
"User info retrieval not supported via IScmConnector adapter.");
}
#endregion
#region Extended SCM operations (from IScmConnector)
/// <summary>
/// Creates a branch from the base branch.
/// </summary>
public async Task<AdvisoryBranchResult> CreateBranchAsync(
string owner,
string repo,
string branchName,
string baseBranch,
CancellationToken ct)
{
return await _inner.CreateBranchAsync(owner, repo, branchName, baseBranch, ct);
}
/// <summary>
/// Updates or creates a file in a branch.
/// </summary>
public async Task<AdvisoryFileUpdateResult> UpdateFileAsync(
string owner,
string repo,
string branch,
string filePath,
string content,
string commitMessage,
CancellationToken ct)
{
return await _inner.UpdateFileAsync(owner, repo, branch, filePath, content, commitMessage, ct);
}
/// <summary>
/// Creates a pull request / merge request.
/// </summary>
public async Task<AdvisoryPrCreateResult> CreatePullRequestAsync(
string owner,
string repo,
string headBranch,
string baseBranch,
string title,
string body,
CancellationToken ct)
{
return await _inner.CreatePullRequestAsync(owner, repo, headBranch, baseBranch, title, body, ct);
}
/// <summary>
/// Gets pull request details and status.
/// </summary>
public async Task<AdvisoryPrStatusResult> GetPullRequestStatusAsync(
string owner,
string repo,
int prNumber,
CancellationToken ct)
{
return await _inner.GetPullRequestStatusAsync(owner, repo, prNumber, ct);
}
/// <summary>
/// Gets CI/CD pipeline status for a commit.
/// </summary>
public async Task<AdvisoryCiStatusResult> GetCiStatusAsync(
string owner,
string repo,
string commitSha,
CancellationToken ct)
{
return await _inner.GetCiStatusAsync(owner, repo, commitSha, ct);
}
/// <summary>
/// Updates pull request body/description.
/// </summary>
public async Task<bool> UpdatePullRequestAsync(
string owner,
string repo,
int prNumber,
string? title,
string? body,
CancellationToken ct)
{
return await _inner.UpdatePullRequestAsync(owner, repo, prNumber, title, body, ct);
}
/// <summary>
/// Adds a comment to a pull request.
/// </summary>
public async Task<bool> AddCommentAsync(
string owner,
string repo,
int prNumber,
string comment,
CancellationToken ct)
{
return await _inner.AddCommentAsync(owner, repo, prNumber, comment, ct);
}
/// <summary>
/// Closes a pull request without merging.
/// </summary>
public async Task<bool> ClosePullRequestAsync(
string owner,
string repo,
int prNumber,
CancellationToken ct)
{
return await _inner.ClosePullRequestAsync(owner, repo, prNumber, ct);
}
#endregion
#region IPlugin
/// <inheritdoc />
public async Task InitializeAsync(IPluginContext context, CancellationToken ct)
{
_context = context;
_state = PluginLifecycleState.Initializing;
// Verify the connector is available
if (!_plugin.IsAvailable(_options))
{
_state = PluginLifecycleState.Failed;
throw new InvalidOperationException(
$"SCM connector '{_inner.ScmType}' is not available. Check API token configuration.");
}
_state = PluginLifecycleState.Active;
context.Logger.Info("SCM plugin adapter initialized for {ScmType}", _inner.ScmType);
await Task.CompletedTask;
}
/// <inheritdoc />
public async Task<HealthCheckResult> HealthCheckAsync(CancellationToken ct)
{
try
{
var testResult = await TestConnectionAsync(ct);
if (testResult.Success)
{
return HealthCheckResult.Healthy()
.WithDetails(new Dictionary<string, object>
{
["scmType"] = _inner.ScmType,
["displayName"] = _plugin.DisplayName,
["latencyMs"] = testResult.Latency?.TotalMilliseconds ?? 0
});
}
return HealthCheckResult.Unhealthy(testResult.Message ?? "Connection test failed");
}
catch (Exception ex)
{
return HealthCheckResult.Unhealthy(ex);
}
}
/// <inheritdoc />
public ValueTask DisposeAsync()
{
_state = PluginLifecycleState.Stopped;
return ValueTask.CompletedTask;
}
#endregion
}

View File

@@ -0,0 +1,134 @@
namespace StellaOps.AdvisoryAI.Scm.Plugin.Unified;
using Microsoft.Extensions.DependencyInjection;
using StellaOps.AdvisoryAI.Remediation.ScmConnector;
using StellaOps.Plugin.Abstractions;
using StellaOps.Plugin.Abstractions.Capabilities;
/// <summary>
/// Factory for creating unified SCM plugin adapters from existing connectors.
/// </summary>
public sealed class ScmPluginAdapterFactory
{
private readonly ScmConnectorCatalog _catalog;
private readonly Dictionary<string, ScmPluginAdapter> _adapters = new(StringComparer.OrdinalIgnoreCase);
private readonly object _lock = new();
/// <summary>
/// Creates a new factory instance.
/// </summary>
/// <param name="catalog">The SCM connector catalog.</param>
public ScmPluginAdapterFactory(ScmConnectorCatalog catalog)
{
_catalog = catalog ?? throw new ArgumentNullException(nameof(catalog));
}
/// <summary>
/// Gets all available unified SCM plugins.
/// </summary>
/// <param name="options">Connector options.</param>
/// <returns>List of unified SCM plugins.</returns>
public IReadOnlyList<IPlugin> GetAllPlugins(ScmConnectorOptions options)
{
var result = new List<IPlugin>();
foreach (var plugin in _catalog.Plugins)
{
if (plugin.IsAvailable(options))
{
var adapter = GetOrCreateAdapter(plugin.ScmType, options);
if (adapter != null)
{
result.Add(adapter);
}
}
}
return result;
}
/// <summary>
/// Gets a unified SCM plugin by SCM type.
/// </summary>
/// <param name="scmType">SCM type identifier.</param>
/// <param name="options">Connector options.</param>
/// <returns>Unified SCM plugin, or null if not found.</returns>
public IPlugin? GetPlugin(string scmType, ScmConnectorOptions options)
{
return GetOrCreateAdapter(scmType, options);
}
/// <summary>
/// Gets a unified SCM plugin that can handle the given repository URL.
/// </summary>
/// <param name="repositoryUrl">Repository URL to handle.</param>
/// <param name="options">Connector options.</param>
/// <returns>Unified SCM plugin, or null if not found.</returns>
public IPlugin? GetPluginForRepository(string repositoryUrl, ScmConnectorOptions options)
{
var plugin = _catalog.Plugins.FirstOrDefault(p => p.CanHandle(repositoryUrl));
if (plugin == null)
return null;
return GetOrCreateAdapter(plugin.ScmType, options);
}
/// <summary>
/// Gets the SCM capability for a connector.
/// </summary>
/// <param name="scmType">SCM type identifier.</param>
/// <param name="options">Connector options.</param>
/// <returns>SCM capability, or null if not found.</returns>
public IScmCapability? GetCapability(string scmType, ScmConnectorOptions options)
{
return GetOrCreateAdapter(scmType, options);
}
private ScmPluginAdapter? GetOrCreateAdapter(string scmType, ScmConnectorOptions options)
{
lock (_lock)
{
if (_adapters.TryGetValue(scmType, out var existing))
{
return existing;
}
var plugin = _catalog.Plugins.FirstOrDefault(
p => p.ScmType.Equals(scmType, StringComparison.OrdinalIgnoreCase));
if (plugin == null || !plugin.IsAvailable(options))
{
return null;
}
var connector = _catalog.GetConnector(scmType, options);
if (connector == null)
{
return null;
}
var adapter = new ScmPluginAdapter(connector, plugin, options);
_adapters[scmType] = adapter;
return adapter;
}
}
}
/// <summary>
/// Extension methods for registering unified SCM plugin services.
/// </summary>
public static class ScmPluginAdapterExtensions
{
/// <summary>
/// Adds unified SCM plugin adapter services to the service collection.
/// </summary>
/// <param name="services">Service collection.</param>
/// <returns>Service collection for chaining.</returns>
public static IServiceCollection AddUnifiedScmPlugins(this IServiceCollection services)
{
services.AddSingleton<ScmPluginAdapterFactory>();
return services;
}
}

View File

@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Description>Unified plugin adapter for AdvisoryAI SCM connectors</Description>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.AdvisoryAI\StellaOps.AdvisoryAI.csproj" />
<ProjectReference Include="..\..\Plugin\StellaOps.Plugin.Abstractions\StellaOps.Plugin.Abstractions.csproj" />
</ItemGroup>
</Project>