release orchestrator v1 draft and build fixes
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user