aboutsummaryrefslogtreecommitdiff
path: root/lib/VNLib.Plugins.Extensions.Loading
diff options
context:
space:
mode:
Diffstat (limited to 'lib/VNLib.Plugins.Extensions.Loading')
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/AssemblyLoader.cs128
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationExtensions.cs10
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs47
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/LoggingExtensions.cs84
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/RoutingExtensions.cs48
-rw-r--r--lib/VNLib.Plugins.Extensions.Loading/src/Secrets/VaultSecrets.cs19
6 files changed, 222 insertions, 114 deletions
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/AssemblyLoader.cs b/lib/VNLib.Plugins.Extensions.Loading/src/AssemblyLoader.cs
index e01b32d..cd2dbad 100644
--- a/lib/VNLib.Plugins.Extensions.Loading/src/AssemblyLoader.cs
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/AssemblyLoader.cs
@@ -24,21 +24,20 @@
using System;
using System.IO;
-using System.Linq;
using System.Threading;
using System.Reflection;
using System.Runtime.Loader;
-using System.Runtime.InteropServices;
using VNLib.Utils.IO;
using VNLib.Utils.Resources;
namespace VNLib.Plugins.Extensions.Loading
{
+
/// <summary>
/// <para>
/// Represents a disposable assembly loader wrapper for
- /// exporting a signle type from a loaded assembly
+ /// exporting a single type from a loaded assembly
/// </para>
/// <para>
/// If the loaded type implements <see cref="IDisposable"/> the
@@ -46,79 +45,26 @@ namespace VNLib.Plugins.Extensions.Loading
/// </para>
/// </summary>
/// <typeparam name="T">The exported type to manage</typeparam>
- public sealed class AssemblyLoader<T> : OpenResourceHandle<T>
+ public sealed class AssemblyLoader<T> : ManagedLibrary, IDisposable
{
private readonly CancellationTokenRegistration _reg;
private readonly Lazy<T> _instance;
- private readonly AssemblyLoadContext _loadContext;
- private readonly AssemblyDependencyResolver _resolver;
- private readonly string _assemblyPath;
+ private bool disposedValue;
/// <summary>
/// The instance of the loaded type
/// </summary>
- public override T Resource => _instance.Value;
+ public T Resource => _instance.Value;
private AssemblyLoader(string assemblyPath, AssemblyLoadContext parentContext, CancellationToken unloadToken)
+ :base(assemblyPath, parentContext)
{
- _loadContext = parentContext;
- _resolver = new(assemblyPath);
- _assemblyPath = assemblyPath;
-
- //Add resolver for context
- parentContext.Resolving += OnDependencyResolving;
- parentContext.ResolvingUnmanagedDll += OnNativeLibraryResolving;
-
- //Init lazy loader
- _instance = new(LoadAndGetExportedType, LazyThreadSafetyMode.PublicationOnly);
+ //Init lazy type loader
+ _instance = new(LoadTypeFromAssembly<T>, LazyThreadSafetyMode.PublicationOnly);
//Register dispose
_reg = unloadToken.Register(Dispose);
}
-
- /*
- * Resolves a native libary isolated to the requested assembly, which
- * should be isolated to this assembly or one of its dependencies.
- */
- private IntPtr OnNativeLibraryResolving(Assembly assembly, string libname)
- {
- //Resolve the desired asm dependency for the current context
- string? requestedDll = _resolver.ResolveUnmanagedDllToPath(libname);
-
- //if the dep is resolved, seach in the assembly directory for the manageed dll only
- return requestedDll == null ? IntPtr.Zero : NativeLibrary.Load(requestedDll, assembly, DllImportSearchPath.AssemblyDirectory);
- }
-
- private Assembly? OnDependencyResolving(AssemblyLoadContext context, AssemblyName asmName)
- {
- //Resolve the desired asm dependency for the current context
- string? desiredAsm = _resolver.ResolveAssemblyToPath(asmName);
-
- //If the asm exists in the dir, load it
- return desiredAsm == null ? null : _loadContext.LoadFromAssemblyPath(desiredAsm);
- }
-
- /// <summary>
- /// Loads the default assembly and gets the expected export type,
- /// creates a new instance, and calls its parameterless constructor
- /// </summary>
- /// <returns>The desired type instance</returns>
- /// <exception cref="EntryPointNotFoundException"></exception>
- private T LoadAndGetExportedType()
- {
- //Load the assembly into the parent context
- Assembly asm = _loadContext.LoadFromAssemblyPath(_assemblyPath);
-
- Type resourceType = typeof(T);
-
- //See if the type is exported
- Type exp = (from type in asm.GetExportedTypes()
- where resourceType.IsAssignableFrom(type)
- select type)
- .FirstOrDefault()
- ?? throw new EntryPointNotFoundException($"Imported assembly does not export desired type {resourceType.FullName}");
- //Create instance
- return (T)Activator.CreateInstance(exp)!;
- }
+
/// <summary>
/// Creates a method delegate for the given method name from
@@ -132,24 +78,53 @@ namespace VNLib.Plugins.Extensions.Loading
public TDelegate? TryGetMethod<TDelegate>(string methodName) where TDelegate : Delegate
{
//get the type info of the actual resource
- return Resource!.GetType()
+ return Resource.GetType()
.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance)
?.CreateDelegate<TDelegate>(Resource);
}
- ///<inheritdoc/>
- protected override void Free()
+ private void Dispose(bool disposing)
{
- //Remove resolving event handlers
- _loadContext.Resolving -= OnDependencyResolving;
- _loadContext.ResolvingUnmanagedDll -= OnNativeLibraryResolving;
-
- //If the instance is disposable, call its dispose method on unload
- if (_instance.IsValueCreated && _instance.Value is IDisposable)
+ if (!disposedValue)
{
- (_instance.Value as IDisposable)?.Dispose();
+ //Call base unload during dispose (or finalize)
+ OnUnload();
+
+ //Always cleanup registration
+ _reg.Dispose();
+
+ if (disposing)
+ {
+ //If the instance is disposable, call its dispose method on unload
+ if (_instance.IsValueCreated && _instance.Value is IDisposable)
+ {
+ (_instance.Value as IDisposable)?.Dispose();
+ }
+ }
+
+ disposedValue = true;
}
- _reg.Dispose();
+ }
+
+ /// <summary>
+ /// Cleans up any unused internals
+ /// </summary>
+ ~AssemblyLoader()
+ {
+ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ Dispose(disposing: false);
+ }
+
+
+ /// <summary>
+ /// Disposes the assembly loader and cleans up resources. If the <typeparamref name="T"/>
+ /// inherits <see cref="IDisposable"/> the intrance is disposed.
+ /// </summary>
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
}
/// <summary>
@@ -171,8 +146,9 @@ namespace VNLib.Plugins.Extensions.Loading
throw new FileNotFoundException($"The desired assembly {assemblyName} could not be found at the file path");
}
- return new(assemblyName, loadContext, unloadToken);
+ //Create the loader from its absolute file path
+ FileInfo fi = new(assemblyName);
+ return new(fi.FullName, loadContext, unloadToken);
}
-
}
}
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationExtensions.cs b/lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationExtensions.cs
index fbe8d48..adfd997 100644
--- a/lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationExtensions.cs
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/ConfigurationExtensions.cs
@@ -134,7 +134,8 @@ namespace VNLib.Plugins.Extensions.Loading
{
plugin.ThrowIfUnloaded();
//Try to get the element from the plugin config first, or fallback to host
- if (plugin.PluginConfig.TryGetProperty(propName, out JsonElement el) || plugin.HostConfig.TryGetProperty(propName, out el))
+ if (plugin.PluginConfig.TryGetProperty(propName, out JsonElement el)
+ || plugin.HostConfig.TryGetProperty(propName, out el))
{
//Get the top level config as a dictionary
return new ConfigScope(el, propName);
@@ -202,7 +203,7 @@ namespace VNLib.Plugins.Extensions.Loading
string? configName = GetConfigNameForType(type);
if (configName != null)
{
- throw new KeyNotFoundException($"Missing required configuration key {configName} for type {type.Name}");
+ throw new KeyNotFoundException($"Missing required configuration key '{configName}' for type {type.Name}");
}
else
{
@@ -259,7 +260,10 @@ namespace VNLib.Plugins.Extensions.Loading
Type type = typeof(T);
ConfigurationNameAttribute? configName = type.GetCustomAttribute<ConfigurationNameAttribute>();
//See if the plugin contains a configuration varables
- return configName != null && plugin.PluginConfig.TryGetProperty(configName.ConfigVarName, out _);
+ return configName != null && (
+ plugin.PluginConfig.TryGetProperty(configName.ConfigVarName, out _) ||
+ plugin.HostConfig.TryGetProperty(configName.ConfigVarName, out _)
+ );
}
/// <summary>
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs b/lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs
index 8f7dee8..3da015e 100644
--- a/lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/LoadingExtensions.cs
@@ -561,33 +561,40 @@ namespace VNLib.Plugins.Extensions.Loading
object service;
- //Determin configuration requirments
- if (ConfigurationExtensions.ConfigurationRequired(serviceType) || config != null)
+ try
{
- if (config == null)
+ //Determin configuration requirments
+ if (ConfigurationExtensions.ConfigurationRequired(serviceType) || config != null)
{
- ConfigurationExtensions.ThrowConfigNotFoundForType(serviceType);
- }
+ if (config == null)
+ {
+ ConfigurationExtensions.ThrowConfigNotFoundForType(serviceType);
+ }
- //Get the constructor for required or available config
- ConstructorInfo? constructor = serviceType.GetConstructor(new Type[] { typeof(PluginBase), typeof(IConfigScope) });
+ //Get the constructor for required or available config
+ ConstructorInfo? constructor = serviceType.GetConstructor(new Type[] { typeof(PluginBase), typeof(IConfigScope) });
- //Make sure the constructor exists
- _ = constructor ?? throw new EntryPointNotFoundException($"No constructor found for {serviceType.Name}");
+ //Make sure the constructor exists
+ _ = constructor ?? throw new EntryPointNotFoundException($"No constructor found for {serviceType.Name}");
- //Call constructore
- service = constructor.Invoke(new object[2] { plugin, config });
- }
- else
- {
- //Get the constructor
- ConstructorInfo? constructor = serviceType.GetConstructor(new Type[] { typeof(PluginBase) });
+ //Call constructore
+ service = constructor.Invoke(new object[2] { plugin, config });
+ }
+ else
+ {
+ //Get the constructor
+ ConstructorInfo? constructor = serviceType.GetConstructor(new Type[] { typeof(PluginBase) });
- //Make sure the constructor exists
- _ = constructor ?? throw new EntryPointNotFoundException($"No constructor found for {serviceType.Name}");
+ //Make sure the constructor exists
+ _ = constructor ?? throw new EntryPointNotFoundException($"No constructor found for {serviceType.Name}");
- //Call constructore
- service = constructor.Invoke(new object[1] { plugin });
+ //Call constructore
+ service = constructor.Invoke(new object[1] { plugin });
+ }
+ }
+ catch(TargetInvocationException te) when (te.InnerException != null)
+ {
+ throw te.InnerException;
}
Task? loading = null;
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/LoggingExtensions.cs b/lib/VNLib.Plugins.Extensions.Loading/src/LoggingExtensions.cs
new file mode 100644
index 0000000..47ecdf0
--- /dev/null
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/LoggingExtensions.cs
@@ -0,0 +1,84 @@
+/*
+* Copyright (c) 2023 Vaughn Nugent
+*
+* Library: VNLib
+* Package: VNLib.Plugins.Extensions.Loading
+* File: LoggingExtensions.cs
+*
+* LoggingExtensions.cs is part of VNLib.Plugins.Extensions.Loading which is part of the larger
+* VNLib collection of libraries and utilities.
+*
+* VNLib.Plugins.Extensions.Loading is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License as
+* published by the Free Software Foundation, either version 3 of the
+* License, or (at your option) any later version.
+*
+* VNLib.Plugins.Extensions.Loading is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see https://www.gnu.org/licenses/.
+*/
+
+using System;
+
+using VNLib.Utils.Logging;
+
+namespace VNLib.Plugins.Extensions.Loading
+{
+ /// <summary>
+ /// Provides advanced QOL features for event logging
+ /// </summary>
+ public static class LoggingExtensions
+ {
+ /// <summary>
+ /// Creates a new <see cref="ILogProvider"/> that scopes all log messages to the specified name
+ /// when writing messages
+ /// </summary>
+ /// <param name="log"></param>
+ /// <param name="scopeName">The name of the scope to print log values to</param>
+ /// <returns>The scoped log provider instance</returns>
+ public static ILogProvider CreateScope(this ILogProvider log, string scopeName)
+ {
+ return new ScopeLogProvider(log, scopeName);
+ }
+
+ private sealed record class ScopeLogProvider(ILogProvider Log, string ScopeName) : ILogProvider
+ {
+ ///<inheritdoc/>
+ public void Flush() => Log.Flush();
+
+ ///<inheritdoc/>
+ public object GetLogProvider() => Log.GetLogProvider();
+
+ ///<inheritdoc/>
+ public bool IsEnabled(LogLevel level) => Log.IsEnabled(level);
+
+ ///<inheritdoc/>
+ public void Write(LogLevel level, string value)
+ {
+ Log.Write(level, $"[{ScopeName}]: {value}");
+ }
+
+ ///<inheritdoc/>
+ public void Write(LogLevel level, Exception exception, string value = "")
+ {
+ Log.Write(level, exception, $"[{ScopeName}]: {value}");
+ }
+
+ ///<inheritdoc/>
+ public void Write(LogLevel level, string value, params object?[] args)
+ {
+ Log.Write(level, $"[{ScopeName}]: {value}", args);
+ }
+
+ ///<inheritdoc/>
+ public void Write(LogLevel level, string value, params ValueType[] args)
+ {
+ Log.Write(level, $"[{ScopeName}]: {value}", args);
+ }
+ }
+ }
+}
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/RoutingExtensions.cs b/lib/VNLib.Plugins.Extensions.Loading/src/RoutingExtensions.cs
index 22686f0..7731bcf 100644
--- a/lib/VNLib.Plugins.Extensions.Loading/src/RoutingExtensions.cs
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/RoutingExtensions.cs
@@ -46,9 +46,10 @@ namespace VNLib.Plugins.Extensions.Loading.Routing
/// </summary>
/// <typeparam name="T">The <see cref="IEndpoint"/> type</typeparam>
/// <param name="plugin"></param>
+ /// <param name="configRequired">If true, requires the configuration exist in the config file</param>
/// <param name="pluginConfigPathName">The path to the plugin sepcific configuration property</param>
/// <exception cref="TargetInvocationException"></exception>
- public static T Route<T>(this PluginBase plugin, string? pluginConfigPathName) where T : IEndpoint
+ public static T Route<T>(this PluginBase plugin, string? pluginConfigPathName, bool configRequired = true) where T : IEndpoint
{
Type endpointType = typeof(T);
@@ -59,7 +60,7 @@ namespace VNLib.Plugins.Extensions.Loading.Routing
{
ConstructorInfo? constructor = endpointType.GetConstructor(new Type[] { typeof(PluginBase) });
- _ = constructor ?? throw new EntryPointNotFoundException($"No constructor found for {endpointType.Name}");
+ _ = constructor ?? throw new EntryPointNotFoundException($"No constructor t(PluginBase p) found for {endpointType.Name}");
//Create the new endpoint and pass the plugin instance
endpoint = (T)constructor.Invoke(new object[] { plugin });
@@ -69,19 +70,42 @@ namespace VNLib.Plugins.Extensions.Loading.Routing
}
else
{
- ConstructorInfo? constructor = endpointType.GetConstructor(new Type[] { typeof(PluginBase), typeof(IConfigScope) });
+ //Try to get config but allow null if not required
+ IConfigScope? config = plugin.TryGetConfig(pluginConfigPathName);
- //Make sure the constructor exists
- _ = constructor ?? throw new EntryPointNotFoundException($"No constructor found for {endpointType.Name}");
+ if(configRequired && config == null)
+ {
+ ConfigurationExtensions.ThrowConfigNotFoundForType(endpointType);
+ return default;
+ }
- //Get config variables for the endpoint
- IConfigScope conf = plugin.GetConfig(pluginConfigPathName);
+ //Choose constructor based on config
+ if (config != null)
+ {
+ ConstructorInfo? constructor = endpointType.GetConstructor(new Type[] { typeof(PluginBase), typeof(IConfigScope) });
- //Create the new endpoint and pass the plugin instance along with the configuration object
- endpoint = (T)constructor.Invoke(new object[] { plugin, conf });
+ //Make sure the constructor exists
+ _ = constructor ?? throw new EntryPointNotFoundException($"No constructor t(PluginBase p, IConfigScope cs) found for {endpointType.Name}");
- //Register event handlers for the endpoint
- ScheduleIntervals(plugin, endpoint, endpointType, conf);
+ //Create the new endpoint and pass the plugin instance along with the configuration object
+ endpoint = (T)constructor.Invoke(new object[] { plugin, config });
+
+ //Register event handlers for the endpoint
+ ScheduleIntervals(plugin, endpoint, endpointType, config);
+ }
+ else
+ {
+ //Config does not exist, so use the default constructor
+ ConstructorInfo? constructor = endpointType.GetConstructor(new Type[] { typeof(PluginBase) });
+
+ _ = constructor ?? throw new EntryPointNotFoundException($"No constructor t(PluginBase p) found for {endpointType.Name}");
+
+ //Create the new endpoint and pass the plugin instance
+ endpoint = (T)constructor.Invoke(new object[] { plugin });
+
+ //Register event handlers for the endpoint
+ ScheduleIntervals(plugin, endpoint, endpointType, null);
+ }
}
//Route the endpoint
@@ -112,7 +136,7 @@ namespace VNLib.Plugins.Extensions.Loading.Routing
//Get config name attribute
ConfigurationNameAttribute? configAttr = endpointType.GetCustomAttribute<ConfigurationNameAttribute>();
//Route using attribute
- return plugin.Route<T>(configAttr?.ConfigVarName);
+ return plugin.Route<T>(configAttr?.ConfigVarName, configAttr?.Required == true);
}
/// <summary>
diff --git a/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/VaultSecrets.cs b/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/VaultSecrets.cs
index 711ae50..08af485 100644
--- a/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/VaultSecrets.cs
+++ b/lib/VNLib.Plugins.Extensions.Loading/src/Secrets/VaultSecrets.cs
@@ -61,6 +61,7 @@ namespace VNLib.Plugins.Extensions.Loading
public const string VAULT_URL_KEY = "url";
public const string VAULT_URL_SCHEME = "vault://";
+ public const string ENV_URL_SCHEME = "env://";
/// <summary>
@@ -110,12 +111,23 @@ namespace VNLib.Plugins.Extensions.Loading
}
//Secret is a vault path, or return the raw value
- if (!rawSecret.StartsWith(VAULT_URL_SCHEME, StringComparison.OrdinalIgnoreCase))
+ if (rawSecret.StartsWith(VAULT_URL_SCHEME, StringComparison.OrdinalIgnoreCase))
+ {
+ return GetSecretFromVaultAsync(plugin, rawSecret);
+ }
+
+ //See if the secret is an environment variable path
+ if (rawSecret.StartsWith(ENV_URL_SCHEME, StringComparison.OrdinalIgnoreCase))
{
- return Task.FromResult<ISecretResult?>(new SecretResult(rawSecret.AsSpan()));
+ //try to get the environment variable
+ string envVar = rawSecret[ENV_URL_SCHEME.Length..];
+ string? envVal = Environment.GetEnvironmentVariable(envVar);
+
+ return Task.FromResult<ISecretResult?>(envVal == null ? null : new SecretResult(envVal));
}
- return GetSecretFromVaultAsync(plugin, rawSecret);
+ //Finally, return the raw value
+ return Task.FromResult<ISecretResult?>(new SecretResult(rawSecret.AsSpan()));
}
/// <summary>
@@ -197,6 +209,7 @@ namespace VNLib.Plugins.Extensions.Loading
{
return Task.FromResult<X509Certificate?>(new (rawSecret));
}
+
return GetCertFromVaultAsync(plugin, rawSecret);
}