155 lines
5.2 KiB
C#
155 lines
5.2 KiB
C#
|
|
using Microsoft.Extensions.Logging;
|
|
using StellaOps.Plugin.Abstractions;
|
|
using StellaOps.Plugin.Abstractions.Attributes;
|
|
using StellaOps.Plugin.Abstractions.Manifest;
|
|
using System.Reflection;
|
|
|
|
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 = []
|
|
};
|
|
}
|
|
}
|