release orchestrator v1 draft and build fixes
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
using System.Reflection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Plugin.Abstractions;
|
||||
using StellaOps.Plugin.Abstractions.Attributes;
|
||||
using StellaOps.Plugin.Abstractions.Manifest;
|
||||
|
||||
namespace StellaOps.Plugin.Host.Discovery;
|
||||
|
||||
/// <summary>
|
||||
/// Discovers plugins embedded in loaded assemblies.
|
||||
/// </summary>
|
||||
public sealed class EmbeddedPluginDiscovery : IPluginDiscovery
|
||||
{
|
||||
private readonly ILogger<EmbeddedPluginDiscovery> _logger;
|
||||
private readonly IReadOnlyList<Assembly> _assemblies;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new embedded plugin discovery instance.
|
||||
/// </summary>
|
||||
/// <param name="logger">Logger instance.</param>
|
||||
/// <param name="assemblies">Optional list of assemblies to scan. If null, scans all loaded assemblies.</param>
|
||||
public EmbeddedPluginDiscovery(
|
||||
ILogger<EmbeddedPluginDiscovery> logger,
|
||||
IEnumerable<Assembly>? assemblies = null)
|
||||
{
|
||||
_logger = logger;
|
||||
_assemblies = assemblies?.ToList() ?? [];
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<PluginManifest>> DiscoverAsync(
|
||||
IEnumerable<string> searchPaths,
|
||||
CancellationToken ct)
|
||||
{
|
||||
// searchPaths are ignored for embedded discovery
|
||||
// We only scan the provided assemblies or the application assemblies
|
||||
var manifests = new List<PluginManifest>();
|
||||
|
||||
var assembliesToScan = _assemblies.Count > 0
|
||||
? _assemblies
|
||||
: GetApplicationAssemblies();
|
||||
|
||||
foreach (var assembly in assembliesToScan)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
try
|
||||
{
|
||||
var pluginTypes = assembly.GetTypes()
|
||||
.Where(t => t.IsClass && !t.IsAbstract && typeof(IPlugin).IsAssignableFrom(t))
|
||||
.ToList();
|
||||
|
||||
foreach (var pluginType in pluginTypes)
|
||||
{
|
||||
try
|
||||
{
|
||||
var manifest = CreateManifestFromType(pluginType);
|
||||
if (manifest != null)
|
||||
{
|
||||
manifests.Add(manifest);
|
||||
_logger.LogDebug(
|
||||
"Discovered embedded plugin {PluginId} in assembly {Assembly}",
|
||||
manifest.Info.Id, assembly.GetName().Name);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex,
|
||||
"Failed to create manifest for type {Type} in assembly {Assembly}",
|
||||
pluginType.FullName, assembly.GetName().Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ReflectionTypeLoadException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to load types from assembly {Assembly}",
|
||||
assembly.GetName().Name);
|
||||
}
|
||||
}
|
||||
|
||||
return Task.FromResult<IReadOnlyList<PluginManifest>>(manifests);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<PluginManifest> DiscoverSingleAsync(PluginSource source, CancellationToken ct)
|
||||
{
|
||||
if (source.Type != PluginSourceType.Embedded)
|
||||
throw new ArgumentException($"Unsupported source type: {source.Type}", nameof(source));
|
||||
|
||||
// Location should be the fully qualified type name
|
||||
var typeName = source.Location;
|
||||
|
||||
foreach (var assembly in GetApplicationAssemblies())
|
||||
{
|
||||
var type = assembly.GetType(typeName);
|
||||
if (type != null && typeof(IPlugin).IsAssignableFrom(type))
|
||||
{
|
||||
var manifest = CreateManifestFromType(type);
|
||||
if (manifest != null)
|
||||
return Task.FromResult(manifest);
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Embedded plugin type not found: {typeName}");
|
||||
}
|
||||
|
||||
private static IReadOnlyList<Assembly> GetApplicationAssemblies()
|
||||
{
|
||||
var entryAssembly = Assembly.GetEntryAssembly();
|
||||
if (entryAssembly == null)
|
||||
return AppDomain.CurrentDomain.GetAssemblies();
|
||||
|
||||
var referencedNames = entryAssembly.GetReferencedAssemblies();
|
||||
var assemblies = new List<Assembly> { entryAssembly };
|
||||
|
||||
foreach (var name in referencedNames)
|
||||
{
|
||||
try
|
||||
{
|
||||
assemblies.Add(Assembly.Load(name));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Skip assemblies that can't be loaded
|
||||
}
|
||||
}
|
||||
|
||||
return assemblies;
|
||||
}
|
||||
|
||||
private static PluginManifest? CreateManifestFromType(Type pluginType)
|
||||
{
|
||||
var attribute = pluginType.GetCustomAttribute<PluginAttribute>();
|
||||
if (attribute == null)
|
||||
return null;
|
||||
|
||||
return new PluginManifest
|
||||
{
|
||||
Info = new PluginInfo(
|
||||
Id: attribute.Id,
|
||||
Name: attribute.Name ?? pluginType.Name,
|
||||
Version: attribute.Version ?? "1.0.0",
|
||||
Vendor: attribute.Vendor ?? "Unknown",
|
||||
Description: attribute.Description),
|
||||
EntryPoint = pluginType.FullName!,
|
||||
AssemblyPath = pluginType.Assembly.Location,
|
||||
Capabilities = [],
|
||||
Dependencies = [],
|
||||
Permissions = [],
|
||||
Tags = []
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user