aboutsummaryrefslogtreecommitdiff
path: root/VNLib.Plugins.Extensions.Loading
diff options
context:
space:
mode:
Diffstat (limited to 'VNLib.Plugins.Extensions.Loading')
-rw-r--r--VNLib.Plugins.Extensions.Loading/AssemblyLoader.cs162
-rw-r--r--VNLib.Plugins.Extensions.Loading/ConfigurationExtensions.cs200
-rw-r--r--VNLib.Plugins.Extensions.Loading/Events/AsyncIntervalAttribute.cs47
-rw-r--r--VNLib.Plugins.Extensions.Loading/Events/ConfigurableAsyncIntervalAttribute.cs51
-rw-r--r--VNLib.Plugins.Extensions.Loading/Events/EventManagment.cs125
-rw-r--r--VNLib.Plugins.Extensions.Loading/Events/IIntervalScheduleable.cs45
-rw-r--r--VNLib.Plugins.Extensions.Loading/Events/IntervalResultionType.cs49
-rw-r--r--VNLib.Plugins.Extensions.Loading/LoadingExtensions.cs334
-rw-r--r--VNLib.Plugins.Extensions.Loading/PrivateKey.cs102
-rw-r--r--VNLib.Plugins.Extensions.Loading/RoutingExtensions.cs161
-rw-r--r--VNLib.Plugins.Extensions.Loading/S3Config.cs43
-rw-r--r--VNLib.Plugins.Extensions.Loading/SecretResult.cs61
-rw-r--r--VNLib.Plugins.Extensions.Loading/UserLoading.cs93
-rw-r--r--VNLib.Plugins.Extensions.Loading/VNLib.Plugins.Extensions.Loading.csproj39
-rw-r--r--VNLib.Plugins.Extensions.Loading/VNLib.Plugins.Extensions.Loading.xml260
-rw-r--r--VNLib.Plugins.Extensions.Loading/VaultSecrets.cs453
16 files changed, 0 insertions, 2225 deletions
diff --git a/VNLib.Plugins.Extensions.Loading/AssemblyLoader.cs b/VNLib.Plugins.Extensions.Loading/AssemblyLoader.cs
deleted file mode 100644
index 5baf123..0000000
--- a/VNLib.Plugins.Extensions.Loading/AssemblyLoader.cs
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Extensions.Loading
-* File: AssemblyLoader.cs
-*
-* AssemblyLoader.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 General Public License as published
-* by the Free Software Foundation, either version 2 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
-* General Public License for more details.
-*
-* You should have received a copy of the GNU General Public License
-* along with VNLib.Plugins.Extensions.Loading. If not, see http://www.gnu.org/licenses/.
-*/
-
-using System;
-using System.Linq;
-using System.Threading;
-using System.Reflection;
-using System.Runtime.Loader;
-using System.Collections.Generic;
-
-using McMaster.NETCore.Plugins;
-
-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
- /// </para>
- /// <para>
- /// If the loaded type implements <see cref="IDisposable"/> the
- /// dispose method is called when the loader is disposed
- /// </para>
- /// </summary>
- /// <typeparam name="T">The exported type to manage</typeparam>
- public class AssemblyLoader<T> : OpenResourceHandle<T>
- {
- private readonly PluginLoader _loader;
- private readonly CancellationTokenRegistration _reg;
- private readonly Lazy<T> _instance;
-
- /// <summary>
- /// The instance of the loaded type
- /// </summary>
- public override T Resource => _instance.Value;
-
- private AssemblyLoader(PluginLoader loader, in CancellationToken unloadToken)
- {
- _loader = loader;
- //Init lazy loader
- _instance = new(LoadAndGetExportedType, LazyThreadSafetyMode.PublicationOnly);
- //Register dispose
- _reg = unloadToken.Register(Dispose);
- }
-
- /// <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
- Assembly asm = _loader.LoadDefaultAssembly();
-
- 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
- /// the instance wrapped by the current loader
- /// </summary>
- /// <typeparam name="TDelegate"></typeparam>
- /// <param name="methodName">The name of the method to recover</param>
- /// <returns>The delegate method wrapper if found, null otherwise</returns>
- /// <exception cref="ArgumentNullException"></exception>
- /// <exception cref="AmbiguousMatchException"></exception>
- public TDelegate? TryGetMethod<TDelegate>(string methodName) where TDelegate : Delegate
- {
- //get the type info of the actual resource
- return Resource!.GetType()
- .GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance)
- ?.CreateDelegate<TDelegate>(Resource);
- }
-
- ///<inheritdoc/>
- protected override void Free()
- {
- //If the instance is disposable, call its dispose method on unload
- if (_instance.IsValueCreated && _instance.Value is IDisposable)
- {
- (_instance.Value as IDisposable)?.Dispose();
- }
- _loader.Dispose();
- _reg.Dispose();
- }
-
- /// <summary>
- /// Creates a new assembly loader for the specified type and
- /// </summary>
- /// <param name="assemblyName">The name of the assmbly within the current plugin directory</param>
- /// <param name="unloadToken">The plugin unload token</param>
- internal static AssemblyLoader<T> Load(string assemblyName, CancellationToken unloadToken)
- {
- Assembly executingAsm = Assembly.GetExecutingAssembly();
- AssemblyLoadContext currentCtx = AssemblyLoadContext.GetLoadContext(executingAsm) ?? throw new InvalidOperationException("Could not get default assembly load context");
-
- List<Type> shared = new ()
- {
- typeof(T),
- typeof(PluginBase),
- };
-
- //Share all VNLib internal libraries
- shared.AddRange(currentCtx.Assemblies.Where(static s => s.FullName.Contains("VNLib", StringComparison.OrdinalIgnoreCase)).SelectMany(static s => s.GetExportedTypes()));
-
- PluginLoader loader = PluginLoader.CreateFromAssemblyFile(assemblyName,
- currentCtx.IsCollectible,
- shared.ToArray(),
- conf =>
- {
-
- /*
- * Load context is required to be set to the executing assembly's load context
- * because it is controlled by the host, so this loader should be considered a
- * a "child" collection of assemblies
- */
- conf.DefaultContext = currentCtx;
-
- conf.PreferSharedTypes = true;
-
- //Share utils asm
- conf.SharedAssemblies.Add(typeof(Utils.Memory.Memory).Assembly.GetName());
- });
-
- return new(loader, in unloadToken);
- }
- }
-}
diff --git a/VNLib.Plugins.Extensions.Loading/ConfigurationExtensions.cs b/VNLib.Plugins.Extensions.Loading/ConfigurationExtensions.cs
deleted file mode 100644
index 21f2fcb..0000000
--- a/VNLib.Plugins.Extensions.Loading/ConfigurationExtensions.cs
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Extensions.Loading
-* File: ConfigurationExtensions.cs
-*
-* ConfigurationExtensions.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 General Public License as published
-* by the Free Software Foundation, either version 2 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
-* General Public License for more details.
-*
-* You should have received a copy of the GNU General Public License
-* along with VNLib.Plugins.Extensions.Loading. If not, see http://www.gnu.org/licenses/.
-*/
-
-using System;
-using System.Linq;
-using System.Text.Json;
-using System.Reflection;
-using System.Collections.Generic;
-
-using VNLib.Utils.Extensions;
-
-namespace VNLib.Plugins.Extensions.Loading
-{
- /// <summary>
- /// Specifies a configuration variable name in the plugin's configuration
- /// containing data specific to the type
- /// </summary>
- [AttributeUsage(AttributeTargets.Class)]
- public sealed class ConfigurationNameAttribute : Attribute
- {
- /// <summary>
- ///
- /// </summary>
- public string ConfigVarName { get; }
-
- /// <summary>
- /// Initializes a new <see cref="ConfigurationNameAttribute"/>
- /// </summary>
- /// <param name="configVarName">The name of the configuration variable for the class</param>
- public ConfigurationNameAttribute(string configVarName)
- {
- ConfigVarName = configVarName;
- }
- }
-
- /// <summary>
- /// Contains extensions for plugin configuration specifc extensions
- /// </summary>
- public static class ConfigurationExtensions
- {
- public const string S3_CONFIG = "s3_config";
- public const string S3_SECRET_KEY = "s3_secret";
-
- /// <summary>
- /// Retrieves a top level configuration dictionary of elements for the specified type.
- /// The type must contain a <see cref="ConfigurationNameAttribute"/>
- /// </summary>
- /// <typeparam name="T">The type to get the configuration of</typeparam>
- /// <param name="plugin"></param>
- /// <returns>A <see cref="Dictionary{TKey, TValue}"/> of top level configuration elements for the type</returns>
- /// <exception cref="ObjectDisposedException"></exception>
- public static IReadOnlyDictionary<string, JsonElement> GetConfigForType<T>(this PluginBase plugin)
- {
- Type t = typeof(T);
- return plugin.GetConfigForType(t);
- }
- /// <summary>
- /// Retrieves a top level configuration dictionary of elements with the specified property name,
- /// from the plugin config first, or falls back to the host config file
- /// </summary>
- /// <param name="plugin"></param>
- /// <param name="propName">The config property name to retrieve</param>
- /// <returns>A <see cref="Dictionary{TKey, TValue}"/> of top level configuration elements for the type</returns>
- /// <exception cref="KeyNotFoundException"></exception>
- /// <exception cref="ObjectDisposedException"></exception>
- public static IReadOnlyDictionary<string, JsonElement> GetConfig(this PluginBase plugin, string propName)
- {
- plugin.ThrowIfUnloaded();
- try
- {
- //Try to get the element from the plugin config first
- if (!plugin.PluginConfig.TryGetProperty(propName, out JsonElement el))
- {
- //Fallback to the host config
- el = plugin.HostConfig.GetProperty(propName);
- }
- //Get the top level config as a dictionary
- return el.EnumerateObject().ToDictionary(static k => k.Name, static k => k.Value);
- }
- catch(KeyNotFoundException)
- {
- throw new KeyNotFoundException($"Missing required top level configuration object '{propName}', in host/plugin configuration files");
- }
- }
- /// <summary>
- /// Retrieves a top level configuration dictionary of elements with the specified property name,
- /// from the plugin config first, or falls back to the host config file
- /// </summary>
- /// <param name="plugin"></param>
- /// <param name="propName">The config property name to retrieve</param>
- /// <returns>A <see cref="Dictionary{TKey, TValue}"/> of top level configuration elements for the type</returns>
- /// <exception cref="ObjectDisposedException"></exception>
- public static IReadOnlyDictionary<string, JsonElement>? TryGetConfig(this PluginBase plugin, string propName)
- {
- 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))
- {
- //Get the top level config as a dictionary
- return el.EnumerateObject().ToDictionary(static k => k.Name, static k => k.Value);
- }
- //No config found
- return null;
- }
-
- /// <summary>
- /// Retrieves a top level configuration dictionary of elements for the specified type.
- /// The type must contain a <see cref="ConfigurationNameAttribute"/>
- /// </summary>
- /// <param name="plugin"></param>
- /// <param name="type">The type to get configuration data for</param>
- /// <returns>A <see cref="Dictionary{TKey, TValue}"/> of top level configuration elements for the type</returns>
- /// <exception cref="ObjectDisposedException"></exception>
- public static IReadOnlyDictionary<string, JsonElement> GetConfigForType(this PluginBase plugin, Type type)
- {
- //Get config name attribute from plugin type
- ConfigurationNameAttribute? configName = type.GetCustomAttribute<ConfigurationNameAttribute>();
- return configName?.ConfigVarName == null
- ? throw new KeyNotFoundException("No configuration attribute set")
- : plugin.GetConfig(configName.ConfigVarName);
- }
-
- /// <summary>
- /// Shortcut extension for <see cref="GetConfigForType{T}(PluginBase)"/> to get
- /// config of current class
- /// </summary>
- /// <param name="obj">The object that a configuration can be retrieved for</param>
- /// <param name="plugin">The plugin containing configuration variables</param>
- /// <returns>A <see cref="Dictionary{TKey, TValue}"/> of top level configuration elements for the type</returns>
- /// <exception cref="ObjectDisposedException"></exception>
- public static IReadOnlyDictionary<string, JsonElement> GetConfig(this PluginBase plugin, object obj)
- {
- Type t = obj.GetType();
- return plugin.GetConfigForType(t);
- }
-
- /// <summary>
- /// Determines if the current plugin configuration contains the require properties to initialize
- /// the type
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="plugin"></param>
- /// <returns>True if the plugin config contains the require configuration property</returns>
- public static bool HasConfigForType<T>(this PluginBase plugin)
- {
- 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 _);
- }
-
- /// <summary>
- /// Attempts to load the basic S3 configuration variables required
- /// for S3 client access
- /// </summary>
- /// <param name="plugin"></param>
- /// <returns>The S3 configuration object found in the plugin/host configuration</returns>
- public static S3Config? TryGetS3Config(this PluginBase plugin)
- {
- //Try get the config
- IReadOnlyDictionary<string, JsonElement>? s3conf = plugin.TryGetConfig(S3_CONFIG);
- if(s3conf == null)
- {
- return null;
- }
-
- //Try get the elements
- return new()
- {
- BaseBucket = s3conf.GetPropString("bucket"),
- ClientId = s3conf.GetPropString("access_key"),
- ServerAddress = s3conf.GetPropString("server_address"),
- UseSsl = s3conf.TryGetValue("use_ssl", out JsonElement el) && el.GetBoolean(),
- ClientSecret = plugin.TryGetSecretAsync(S3_SECRET_KEY),
- Region = s3conf.GetPropString("region"),
- };
- }
- }
-}
diff --git a/VNLib.Plugins.Extensions.Loading/Events/AsyncIntervalAttribute.cs b/VNLib.Plugins.Extensions.Loading/Events/AsyncIntervalAttribute.cs
deleted file mode 100644
index 85b0b6d..0000000
--- a/VNLib.Plugins.Extensions.Loading/Events/AsyncIntervalAttribute.cs
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Extensions.Loading
-* File: AsyncIntervalAttribute.cs
-*
-* AsyncIntervalAttribute.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 General Public License as published
-* by the Free Software Foundation, either version 2 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
-* General Public License for more details.
-*
-* You should have received a copy of the GNU General Public License
-* along with VNLib.Plugins.Extensions.Loading. If not, see http://www.gnu.org/licenses/.
-*/
-
-using System;
-
-namespace VNLib.Plugins.Extensions.Loading.Events
-{
- /// <summary>
- /// When added to a method schedules it as a callback on a specified interval when
- /// the plugin is loaded, and stops when unloaded
- /// </summary>
- [AttributeUsage(AttributeTargets.Method)]
- public sealed class AsyncIntervalAttribute : Attribute
- {
- internal readonly TimeSpan Interval;
-
- /// <summary>
- /// Intializes the <see cref="AsyncIntervalAttribute"/> with the specified timeout in milliseconds
- /// </summary>
- /// <param name="milliseconds">The interval in milliseconds</param>
- public AsyncIntervalAttribute(int milliseconds)
- {
- Interval = TimeSpan.FromMilliseconds(milliseconds);
- }
- }
-}
diff --git a/VNLib.Plugins.Extensions.Loading/Events/ConfigurableAsyncIntervalAttribute.cs b/VNLib.Plugins.Extensions.Loading/Events/ConfigurableAsyncIntervalAttribute.cs
deleted file mode 100644
index 12c5ec4..0000000
--- a/VNLib.Plugins.Extensions.Loading/Events/ConfigurableAsyncIntervalAttribute.cs
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Extensions.Loading
-* File: ConfigurableAsyncIntervalAttribute.cs
-*
-* ConfigurableAsyncIntervalAttribute.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 General Public License as published
-* by the Free Software Foundation, either version 2 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
-* General Public License for more details.
-*
-* You should have received a copy of the GNU General Public License
-* along with VNLib.Plugins.Extensions.Loading. If not, see http://www.gnu.org/licenses/.
-*/
-
-using System;
-
-namespace VNLib.Plugins.Extensions.Loading.Events
-{
- /// <summary>
- /// When added to a method schedules it as a callback on a specified interval when
- /// the plugin is loaded, and stops when unloaded
- /// </summary>
- [AttributeUsage(AttributeTargets.Method)]
- public sealed class ConfigurableAsyncIntervalAttribute : Attribute
- {
- internal readonly string IntervalPropertyName;
- internal readonly IntervalResultionType Resolution;
-
- /// <summary>
- /// Initializes a <see cref="ConfigurableAsyncIntervalAttribute"/> with the specified
- /// interval property name
- /// </summary>
- /// <param name="configPropName">The configuration property name for the event interval</param>
- /// <param name="resolution">The time resoltion for the event interval</param>
- public ConfigurableAsyncIntervalAttribute(string configPropName, IntervalResultionType resolution)
- {
- IntervalPropertyName = configPropName;
- Resolution = resolution;
- }
- }
-}
diff --git a/VNLib.Plugins.Extensions.Loading/Events/EventManagment.cs b/VNLib.Plugins.Extensions.Loading/Events/EventManagment.cs
deleted file mode 100644
index af55852..0000000
--- a/VNLib.Plugins.Extensions.Loading/Events/EventManagment.cs
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Extensions.Loading
-* File: EventManagment.cs
-*
-* EventManagment.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 General Public License as published
-* by the Free Software Foundation, either version 2 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
-* General Public License for more details.
-*
-* You should have received a copy of the GNU General Public License
-* along with VNLib.Plugins.Extensions.Loading. If not, see http://www.gnu.org/licenses/.
-*/
-
-using System;
-using System.Threading;
-using System.Threading.Tasks;
-
-using VNLib.Utils.Logging;
-
-namespace VNLib.Plugins.Extensions.Loading.Events
-{
-
- /// <summary>
- /// A deletage to form a method signature for shedulable interval callbacks
- /// </summary>
- /// <param name="log">The plugin's default log provider</param>
- /// <param name="pluginExitToken">The plugin's exit token</param>
- /// <returns>A task the represents the asynchronous work</returns>
- public delegate Task AsyncSchedulableCallback(ILogProvider log, CancellationToken pluginExitToken);
-
- /// <summary>
- /// Provides event schedueling extensions for plugins
- /// </summary>
- public static class EventManagment
- {
- /// <summary>
- /// Schedules an asynchronous event interval for the current plugin, that is active until canceled or until the plugin unloads
- /// </summary>
- /// <param name="plugin"></param>
- /// <param name="asyncCallback">An asyncrhonous callback method.</param>
- /// <param name="interval">The event interval</param>
- /// <param name="immediate">A value that indicates if the callback should be run as soon as possible</param>
- /// <returns>An <see cref="EventHandle"/> that can manage the interval state</returns>
- /// <exception cref="ObjectDisposedException"></exception>
- /// <remarks>If exceptions are raised during callback execution, they are written to the plugin's default log provider</remarks>
- public static void ScheduleInterval(this PluginBase plugin, AsyncSchedulableCallback asyncCallback, TimeSpan interval, bool immediate = false)
- {
- plugin.ThrowIfUnloaded();
-
- plugin.Log.Verbose("Interval for {t} scheduled", interval);
-
- //Run interval on plugins bg scheduler
- _ = plugin.DeferTask(() => RunIntervalOnPluginScheduler(plugin, asyncCallback, interval, immediate));
- }
-
- private static async Task RunIntervalOnPluginScheduler(PluginBase plugin, AsyncSchedulableCallback callback, TimeSpan interval, bool immediate)
- {
-
- static async Task RunCallbackAsync(PluginBase plugin, AsyncSchedulableCallback callback)
- {
- try
- {
- //invoke interval callback
- await callback(plugin.Log, plugin.UnloadToken).ConfigureAwait(false);
- }
- catch (OperationCanceledException)
- {
- //unloaded
- plugin.Log.Verbose("Interval callback canceled due to plugin unload or other event cancellation");
- }
- catch (Exception ex)
- {
- plugin.Log.Error(ex, "Unhandled exception raised during timer callback");
- }
- }
-
- //Run callback immediatly if requested
- if (immediate)
- {
- await RunCallbackAsync(plugin, callback);
- }
-
- //Timer loop
- while (true)
- {
- try
- {
- //await delay and wait for plugin cancellation
- await Task.Delay(interval, plugin.UnloadToken);
- }
- catch (TaskCanceledException)
- {
- //Unload token canceled, exit loop
- break;
- }
-
- await RunCallbackAsync(plugin, callback);
- }
- }
-
- /// <summary>
- /// Registers an <see cref="IIntervalScheduleable"/> type's event handler for
- /// raising timed interval events
- /// </summary>
- /// <param name="plugin"></param>
- /// <param name="scheduleable">The instance to schedule for timeouts</param>
- /// <param name="interval">The timeout interval</param>
- /// <returns>An <see cref="EventHandle"/> that can manage the interval state</returns>
- /// <exception cref="ObjectDisposedException"></exception>
- /// <remarks>If exceptions are raised during callback execution, they are written to the plugin's default log provider</remarks>
- public static void ScheduleInterval(this PluginBase plugin, IIntervalScheduleable scheduleable, TimeSpan interval) =>
- ScheduleInterval(plugin, scheduleable.OnIntervalAsync, interval);
- }
-}
diff --git a/VNLib.Plugins.Extensions.Loading/Events/IIntervalScheduleable.cs b/VNLib.Plugins.Extensions.Loading/Events/IIntervalScheduleable.cs
deleted file mode 100644
index 5ff40f4..0000000
--- a/VNLib.Plugins.Extensions.Loading/Events/IIntervalScheduleable.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Extensions.Loading
-* File: IIntervalScheduleable.cs
-*
-* IIntervalScheduleable.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 General Public License as published
-* by the Free Software Foundation, either version 2 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
-* General Public License for more details.
-*
-* You should have received a copy of the GNU General Public License
-* along with VNLib.Plugins.Extensions.Loading. If not, see http://www.gnu.org/licenses/.
-*/
-
-using System.Threading;
-using System.Threading.Tasks;
-
-using VNLib.Utils.Logging;
-
-namespace VNLib.Plugins.Extensions.Loading.Events
-{
- /// <summary>
- /// Exposes a type for asynchronous event schelueling
- /// </summary>
- public interface IIntervalScheduleable
- {
- /// <summary>
- /// A method that is called when the interval time has elapsed
- /// </summary>
- /// <param name="log">The plugin default log provider</param>
- /// <param name="cancellationToken">A token that may cancel an operations if the plugin becomes unloaded</param>
- /// <returns>A task that resolves when the async operation completes</returns>
- Task OnIntervalAsync(ILogProvider log, CancellationToken cancellationToken);
- }
-}
diff --git a/VNLib.Plugins.Extensions.Loading/Events/IntervalResultionType.cs b/VNLib.Plugins.Extensions.Loading/Events/IntervalResultionType.cs
deleted file mode 100644
index d82efc4..0000000
--- a/VNLib.Plugins.Extensions.Loading/Events/IntervalResultionType.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Extensions.Loading
-* File: IntervalResultionType.cs
-*
-* IntervalResultionType.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 General Public License as published
-* by the Free Software Foundation, either version 2 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
-* General Public License for more details.
-*
-* You should have received a copy of the GNU General Public License
-* along with VNLib.Plugins.Extensions.Loading. If not, see http://www.gnu.org/licenses/.
-*/
-
-namespace VNLib.Plugins.Extensions.Loading.Events
-{
- /// <summary>
- /// The configurable event interval resulution type
- /// </summary>
- public enum IntervalResultionType
- {
- /// <summary>
- /// Specifies event interval resolution in milliseconds
- /// </summary>
- Milliseconds,
- /// <summary>
- /// Specifies event interval resolution in seconds
- /// </summary>
- Seconds,
- /// <summary>
- /// Specifies event interval resolution in minutes
- /// </summary>
- Minutes,
- /// <summary>
- /// Specifies event interval resolution in hours
- /// </summary>
- Hours
- }
-}
diff --git a/VNLib.Plugins.Extensions.Loading/LoadingExtensions.cs b/VNLib.Plugins.Extensions.Loading/LoadingExtensions.cs
deleted file mode 100644
index c23f5e2..0000000
--- a/VNLib.Plugins.Extensions.Loading/LoadingExtensions.cs
+++ /dev/null
@@ -1,334 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Extensions.Loading
-* File: LoadingExtensions.cs
-*
-* LoadingExtensions.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 General Public License as published
-* by the Free Software Foundation, either version 2 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
-* General Public License for more details.
-*
-* You should have received a copy of the GNU General Public License
-* along with VNLib.Plugins.Extensions.Loading. If not, see http://www.gnu.org/licenses/.
-*/
-
-using System;
-using System.IO;
-using System.Linq;
-using System.Text.Json;
-using System.Threading.Tasks;
-using System.Collections.Generic;
-using System.Security.Cryptography;
-using System.Runtime.CompilerServices;
-
-using VNLib.Utils;
-using VNLib.Utils.Logging;
-using VNLib.Utils.Extensions;
-using VNLib.Plugins.Essentials.Accounts;
-
-namespace VNLib.Plugins.Extensions.Loading
-{
- /// <summary>
- /// Provides common loading (and unloading when required) extensions for plugins
- /// </summary>
- public static class LoadingExtensions
- {
- public const string DEBUG_CONFIG_KEY = "debug";
- public const string SECRETS_CONFIG_KEY = "secrets";
- public const string PASSWORD_HASHING_KEY = "passwords";
-
- /*
- * Plugin local cache used for storing singletons for a plugin instance
- */
- private static readonly ConditionalWeakTable<PluginBase, PluginLocalCache> _localCache = new();
-
- /// <summary>
- /// Gets a previously cached service singleton for the desired plugin
- /// </summary>
- /// <param name="serviceType">The service instance type</param>
- /// <param name="plugin">The plugin to obtain or build the singleton for</param>
- /// <param name="serviceFactory">The method to produce the singleton</param>
- /// <returns>The cached or newly created singleton</returns>
- public static object GetOrCreateSingleton(PluginBase plugin, Type serviceType, Func<PluginBase, object> serviceFactory)
- {
- Lazy<object>? service;
- //Get local cache
- PluginLocalCache pc = _localCache.GetValue(plugin, PluginLocalCache.Create);
- //Hold lock while get/set the singleton
- lock (pc.SyncRoot)
- {
- //Check if service already exists
- service = pc.GetService(serviceType);
- //publish the service if it isnt loaded yet
- service ??= pc.AddService(serviceType, serviceFactory);
- }
- //Deferred load of the service
- return service.Value;
- }
-
- /// <summary>
- /// Gets a previously cached service singleton for the desired plugin
- /// or creates a new singleton instance for the plugin
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="plugin">The plugin to obtain or build the singleton for</param>
- /// <param name="serviceFactory">The method to produce the singleton</param>
- /// <returns>The cached or newly created singleton</returns>
- public static T GetOrCreateSingleton<T>(PluginBase plugin, Func<PluginBase, T> serviceFactory)
- => (T)GetOrCreateSingleton(plugin, typeof(T), p => serviceFactory(p)!);
-
-
- /// <summary>
- /// Gets the plugins ambient <see cref="PasswordHashing"/> if loaded, or loads it if required. This class will
- /// be unloaded when the plugin us unloaded.
- /// </summary>
- /// <param name="plugin"></param>
- /// <returns>The ambient <see cref="PasswordHashing"/></returns>
- /// <exception cref="OverflowException"></exception>
- /// <exception cref="KeyNotFoundException"></exception>
- /// <exception cref="ObjectDisposedException"></exception>
- public static PasswordHashing GetPasswords(this PluginBase plugin)
- {
- plugin.ThrowIfUnloaded();
- //Get/load the passwords one time only
- return GetOrCreateSingleton(plugin, LoadPasswords);
- }
-
- private static PasswordHashing LoadPasswords(PluginBase plugin)
- {
- PasswordHashing Passwords;
- //Get the global password system secret (pepper)
- byte[] pepper = plugin.TryGetSecretAsync(PASSWORD_HASHING_KEY)
- .ToBase64Bytes().Result ?? throw new KeyNotFoundException($"Missing required key '{PASSWORD_HASHING_KEY}' in secrets");
-
- ERRNO cb(Span<byte> buffer)
- {
- //No longer valid peper if plugin is unloaded as its set to zero, so we need to protect it
- plugin.ThrowIfUnloaded();
-
- pepper.CopyTo(buffer);
- return pepper.Length;
- }
-
- //See hashing params are defined
- IReadOnlyDictionary<string, JsonElement>? hashingArgs = plugin.TryGetConfig(PASSWORD_HASHING_KEY);
- if (hashingArgs != null)
- {
- //Get hashing arguments
- uint saltLen = hashingArgs["salt_len"].GetUInt32();
- uint hashLen = hashingArgs["hash_len"].GetUInt32();
- uint timeCost = hashingArgs["time_cost"].GetUInt32();
- uint memoryCost = hashingArgs["memory_cost"].GetUInt32();
- uint parallelism = hashingArgs["parallelism"].GetUInt32();
- //Load passwords
- Passwords = new(cb, pepper.Length, (int)saltLen, timeCost, memoryCost, parallelism, hashLen);
- }
- else
- {
- //Init default password hashing
- Passwords = new(cb, pepper.Length);
- }
-
- //Register event to cleanup the password class
- _ = plugin.RegisterForUnload(() =>
- {
- //Zero the pepper
- CryptographicOperations.ZeroMemory(pepper);
- });
- //return
- return Passwords;
- }
-
-
- /// <summary>
- /// Loads an assembly into the current plugins AppDomain and will unload when disposed
- /// or the plugin is unloaded from the host application.
- /// </summary>
- /// <typeparam name="T">The desired exported type to load from the assembly</typeparam>
- /// <param name="plugin"></param>
- /// <param name="assemblyName">The name of the assembly (ex: 'file.dll') to search for</param>
- /// <param name="dirSearchOption">Directory/file search option</param>
- /// <returns>The <see cref="AssemblyLoader{T}"/> managing the loaded assmbly in the current AppDomain</returns>
- /// <exception cref="ArgumentNullException"></exception>
- /// <exception cref="FileNotFoundException"></exception>
- /// <exception cref="EntryPointNotFoundException"></exception>
- public static AssemblyLoader<T> LoadAssembly<T>(this PluginBase plugin, string assemblyName, SearchOption dirSearchOption = SearchOption.AllDirectories)
- {
- plugin.ThrowIfUnloaded();
- _ = assemblyName ?? throw new ArgumentNullException(nameof(assemblyName));
-
- //get plugin directory from config
- IReadOnlyDictionary<string, JsonElement> config = plugin.GetConfig("plugins");
- string? pluginsBaseDir = config["path"].GetString();
-
- /*
- * This should never happen since this method can only be called from a
- * plugin context, which means this path was used to load the current plugin
- */
- _ = pluginsBaseDir ?? throw new ArgumentNullException("path", "No plugin path is defined for the current host configuration, this is likely a bug");
-
- //Get the first file that matches the search file
- string? asmFile = Directory.EnumerateFiles(pluginsBaseDir, assemblyName, dirSearchOption).FirstOrDefault();
- _ = asmFile ?? throw new FileNotFoundException($"Failed to load custom assembly {assemblyName} from plugin directory");
-
- //Load the assembly
- return AssemblyLoader<T>.Load(asmFile, plugin.UnloadToken);
- }
-
-
- /// <summary>
- /// Determintes if the current plugin config has a debug propety set
- /// </summary>
- /// <param name="plugin"></param>
- /// <returns>True if debug mode is enabled, false otherwise</returns>
- /// <exception cref="ObjectDisposedException"></exception>
- public static bool IsDebug(this PluginBase plugin)
- {
- plugin.ThrowIfUnloaded();
- //Check for debug element
- return plugin.PluginConfig.TryGetProperty(DEBUG_CONFIG_KEY, out JsonElement dbgEl) && dbgEl.GetBoolean();
- }
-
- /// <summary>
- /// Internal exception helper to raise <see cref="ObjectDisposedException"/> if the plugin has been unlaoded
- /// </summary>
- /// <param name="plugin"></param>
- /// <exception cref="ObjectDisposedException"></exception>
- public static void ThrowIfUnloaded(this PluginBase plugin)
- {
- //See if the plugin was unlaoded
- if (plugin.UnloadToken.IsCancellationRequested)
- {
- throw new ObjectDisposedException("The plugin has been unloaded");
- }
- }
-
- /// <summary>
- /// Schedules an asynchronous callback function to run and its results will be observed
- /// when the operation completes, or when the plugin is unloading
- /// </summary>
- /// <param name="plugin"></param>
- /// <param name="asyncTask">The asynchronous operation to observe</param>
- /// <param name="delayMs">An optional startup delay for the operation</param>
- /// <returns>A task that completes when the deferred task completes </returns>
- /// <exception cref="ObjectDisposedException"></exception>
- public static async Task DeferTask(this PluginBase plugin, Func<Task> asyncTask, int delayMs = 0)
- {
- /*
- * Motivation:
- * Sometimes during plugin loading, a plugin may want to asynchronously load
- * data, where the results are not required to be observed during loading, but
- * should not be pending after the plugin is unloaded, as the assembly may be
- * unloaded and referrences collected by the GC.
- *
- * So we can use the plugin's unload cancellation token to observe the results
- * of a pending async operation
- */
-
- //Test status
- plugin.ThrowIfUnloaded();
-
- //Optional delay
- await Task.Delay(delayMs);
-
- //Run on ts
- Task deferred = Task.Run(asyncTask);
-
- //Add task to deferred list
- plugin.ObserveTask(deferred);
- try
- {
- //Await the task results
- await deferred.ConfigureAwait(false);
- }
- catch(Exception ex)
- {
- //Log errors
- plugin.Log.Error(ex, "Error occured while observing deferred task");
- }
- finally
- {
- //Remove task when complete
- plugin.RemoveObservedTask(deferred);
- }
- }
-
- /// <summary>
- /// Registers an event to occur when the plugin is unloaded on a background thread
- /// and will cause the Plugin.Unload() method to block until the event completes
- /// </summary>
- /// <param name="pbase"></param>
- /// <param name="callback">The method to call when the plugin is unloaded</param>
- /// <returns>A task that represents the registered work</returns>
- /// <exception cref="ArgumentNullException"></exception>
- /// <exception cref="ObjectDisposedException"></exception>
- public static Task RegisterForUnload(this PluginBase pbase, Action callback)
- {
- //Test status
- pbase.ThrowIfUnloaded();
- _ = callback ?? throw new ArgumentNullException(nameof(callback));
-
- //Wait method
- static async Task WaitForUnload(PluginBase pb, Action callback)
- {
- //Wait for unload as a task on the threadpool to avoid deadlocks
- await pb.UnloadToken.WaitHandle.WaitAsync()
- .ConfigureAwait(false);
-
- callback();
- }
-
- //Registaer the task to cause the plugin to wait
- return pbase.DeferTask(() => WaitForUnload(pbase, callback));
- }
-
-
- private sealed class PluginLocalCache
- {
- private readonly PluginBase _plugin;
-
- private readonly Dictionary<Type, Lazy<object>> _store;
-
- public object SyncRoot { get; } = new();
-
- private PluginLocalCache(PluginBase plugin)
- {
- _plugin = plugin;
- _store = new();
- //Register cleanup on unload
- _ = _plugin.RegisterForUnload(() => _store.Clear());
- }
-
- public static PluginLocalCache Create(PluginBase plugin) => new(plugin);
-
-
- public Lazy<object>? GetService(Type serviceType)
- {
- Lazy<object>? t = _store.Where(t => t.Key.IsAssignableTo(serviceType))
- .Select(static tk => tk.Value)
- .FirstOrDefault();
- return t;
- }
-
- public Lazy<object> AddService(Type serviceType, Func<PluginBase, object> factory)
- {
- //Get lazy loader to invoke factory outside of cache lock
- Lazy<object> lazyFactory = new(() => factory(_plugin), true);
- //Store lazy factory
- _store.Add(serviceType, lazyFactory);
- //Pass the lazy factory back
- return lazyFactory;
- }
- }
- }
-}
diff --git a/VNLib.Plugins.Extensions.Loading/PrivateKey.cs b/VNLib.Plugins.Extensions.Loading/PrivateKey.cs
deleted file mode 100644
index 336f6a4..0000000
--- a/VNLib.Plugins.Extensions.Loading/PrivateKey.cs
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Extensions.Loading
-* File: PrivateKey.cs
-*
-* PrivateKey.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 General Public License as published
-* by the Free Software Foundation, either version 2 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
-* General Public License for more details.
-*
-* You should have received a copy of the GNU General Public License
-* along with VNLib.Plugins.Extensions.Loading. If not, see http://www.gnu.org/licenses/.
-*/
-
-using System;
-using System.Text;
-using System.Security.Cryptography;
-
-using VNLib.Utils;
-using VNLib.Utils.Memory;
-using VNLib.Utils.Extensions;
-
-namespace VNLib.Plugins.Extensions.Loading
-{
- /// <summary>
- /// A container for a PKSC#8 encoed private key
- /// </summary>
- public sealed class PrivateKey : VnDisposeable
- {
- private readonly byte[] _utf8RawData;
-
- /// <summary>
- /// Decodes the PKCS#8 encoded private key from a secret, as an EC private key
- /// and recovers the ECDsa algorithm from the key
- /// </summary>
- /// <returns>The <see cref="ECDsa"/> algoritm from the private key</returns>
- /// <exception cref="ArgumentNullException"></exception>
- /// <exception cref="CryptographicException"></exception>
- public ECDsa GetECDsa()
- {
- //Alloc buffer
- using IMemoryHandle<byte> buffer = Memory.SafeAlloc<byte>(_utf8RawData.Length);
- //Get base64 bytes from utf8
- ERRNO count = VnEncoding.Base64UrlDecode(_utf8RawData, buffer.Span);
- //Parse the private key
- ECDsa alg = ECDsa.Create();
- alg.ImportPkcs8PrivateKey(buffer.Span[..(int)count], out _);
- //Wipe the buffer
- Memory.InitializeBlock(buffer.Span);
- return alg;
- }
-
- /// <summary>
- /// Decodes the PKCS#8 encoded private key from a secret, as an RSA private key
- /// </summary>
- /// <returns>The <see cref="RSA"/> algorithm from the private key</returns>
- /// <exception cref="ArgumentNullException"></exception>
- /// <exception cref="CryptographicException"></exception>
- public RSA GetRSA()
- {
- //Alloc buffer
- using IMemoryHandle<byte> buffer = Memory.SafeAlloc<byte>(_utf8RawData.Length);
- //Get base64 bytes from utf8
- ERRNO count = VnEncoding.Base64UrlDecode(_utf8RawData, buffer.Span);
- //Parse the private key
- RSA alg = RSA.Create();
- alg.ImportPkcs8PrivateKey(buffer.Span[..(int)count], out _);
- //Wipe the buffer
- Memory.InitializeBlock(buffer.Span);
- return alg;
- }
-
- internal PrivateKey(SecretResult secret)
- {
- //Alloc and get utf8
- byte[] buffer = new byte[secret.Result.Length];
- int count = Encoding.UTF8.GetBytes(secret.Result, buffer);
- //Verify length
- if(count != buffer.Length)
- {
- throw new FormatException("UTF8 deocde failed");
- }
- //Store
- _utf8RawData = buffer;
- }
-
- protected override void Free()
- {
- Memory.InitializeBlock(_utf8RawData.AsSpan());
- }
- }
-}
diff --git a/VNLib.Plugins.Extensions.Loading/RoutingExtensions.cs b/VNLib.Plugins.Extensions.Loading/RoutingExtensions.cs
deleted file mode 100644
index 9242522..0000000
--- a/VNLib.Plugins.Extensions.Loading/RoutingExtensions.cs
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Extensions.Loading
-* File: RoutingExtensions.cs
-*
-* RoutingExtensions.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 General Public License as published
-* by the Free Software Foundation, either version 2 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
-* General Public License for more details.
-*
-* You should have received a copy of the GNU General Public License
-* along with VNLib.Plugins.Extensions.Loading. If not, see http://www.gnu.org/licenses/.
-*/
-
-using System;
-using System.Linq;
-using System.Text.Json;
-using System.Reflection;
-using System.Collections.Generic;
-using System.Runtime.CompilerServices;
-
-using VNLib.Plugins.Extensions.Loading.Events;
-
-namespace VNLib.Plugins.Extensions.Loading.Routing
-{
- /// <summary>
- /// Provides advanced QOL features to plugin loading
- /// </summary>
- public static class RoutingExtensions
- {
- private static readonly ConditionalWeakTable<IEndpoint, PluginBase?> _pluginRefs = new();
-
- /// <summary>
- /// Constructs and routes the specific endpoint type for the current plugin
- /// </summary>
- /// <typeparam name="T">The <see cref="IEndpoint"/> type</typeparam>
- /// <param name="plugin"></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
- {
- Type endpointType = typeof(T);
- //If the config attribute is not set, then ignore the config variables
- if (string.IsNullOrWhiteSpace(pluginConfigPathName))
- {
- ConstructorInfo? constructor = endpointType.GetConstructor(new Type[] { typeof(PluginBase) });
- _ = constructor ?? throw new EntryPointNotFoundException($"No constructor found for {endpointType.Name}");
- //Create the new endpoint and pass the plugin instance
- T endpoint = (T)constructor.Invoke(new object[] { plugin });
- //Register event handlers for the endpoint
- ScheduleIntervals(plugin, endpoint, endpointType, null);
- //Route the endpoint
- plugin.Route(endpoint);
-
- //Store ref to plugin for endpoint
- _pluginRefs.Add(endpoint, plugin);
-
- return endpoint;
- }
- else
- {
- ConstructorInfo? constructor = endpointType.GetConstructor(new Type[] { typeof(PluginBase), typeof(IReadOnlyDictionary<string, JsonElement>) });
- //Make sure the constructor exists
- _ = constructor ?? throw new EntryPointNotFoundException($"No constructor found for {endpointType.Name}");
- //Get config variables for the endpoint
- IReadOnlyDictionary<string, JsonElement> conf = plugin.GetConfig(pluginConfigPathName);
- //Create the new endpoint and pass the plugin instance along with the configuration object
- T endpoint = (T)constructor.Invoke(new object[] { plugin, conf });
- //Register event handlers for the endpoint
- ScheduleIntervals(plugin, endpoint, endpointType, conf);
- //Route the endpoint
- plugin.Route(endpoint);
-
- //Store ref to plugin for endpoint
- _pluginRefs.Add(endpoint, plugin);
-
- return endpoint;
- }
- }
-
- /// <summary>
- /// Constructs and routes the specific endpoint type for the current plugin
- /// </summary>
- /// <typeparam name="T">The <see cref="IEndpoint"/> type</typeparam>
- /// <param name="plugin"></param>
- /// <exception cref="TargetInvocationException"></exception>
- public static T Route<T>(this PluginBase plugin) where T : IEndpoint
- {
- Type endpointType = typeof(T);
- //Get config name attribute
- ConfigurationNameAttribute? configAttr = endpointType.GetCustomAttribute<ConfigurationNameAttribute>();
- //Route using attribute
- return plugin.Route<T>(configAttr?.ConfigVarName);
- }
-
- /// <summary>
- /// Gets the plugin that loaded the current endpoint
- /// </summary>
- /// <param name="ep"></param>
- /// <returns>The plugin that loaded the current endpoint</returns>
- /// <exception cref="InvalidOperationException"></exception>
- public static PluginBase GetPlugin(this IEndpoint ep)
- {
- _ = _pluginRefs.TryGetValue(ep, out PluginBase? pBase);
- return pBase ?? throw new InvalidOperationException("Endpoint was not dynamically routed");
- }
-
- private static void ScheduleIntervals<T>(PluginBase plugin, T endpointInstance, Type epType, IReadOnlyDictionary<string, JsonElement>? endpointLocalConfig) where T : IEndpoint
- {
- //Get all methods that have the configureable async interval attribute specified
- IEnumerable<Tuple<ConfigurableAsyncIntervalAttribute, AsyncSchedulableCallback>> confIntervals = epType.GetMethods()
- .Where(m => m.GetCustomAttribute<ConfigurableAsyncIntervalAttribute>() != null)
- .Select(m => new Tuple<ConfigurableAsyncIntervalAttribute, AsyncSchedulableCallback>
- (m.GetCustomAttribute<ConfigurableAsyncIntervalAttribute>()!, m.CreateDelegate<AsyncSchedulableCallback>(endpointInstance)));
-
- //If the endpoint has a local config, then use it to find the interval
- if (endpointLocalConfig != null)
- {
-
- //Schedule event handlers on the current plugin
- foreach (Tuple<ConfigurableAsyncIntervalAttribute, AsyncSchedulableCallback> interval in confIntervals)
- {
- int value = endpointLocalConfig[interval.Item1.IntervalPropertyName].GetInt32();
- //Get the timeout from its resolution variable
- TimeSpan timeout = interval.Item1.Resolution switch
- {
- IntervalResultionType.Seconds => TimeSpan.FromSeconds(value),
- IntervalResultionType.Minutes => TimeSpan.FromMinutes(value),
- IntervalResultionType.Hours => TimeSpan.FromHours(value),
- _ => TimeSpan.FromMilliseconds(value),
- };
- //Schedule
- plugin.ScheduleInterval(interval.Item2, timeout);
- }
- }
-
- //Get all methods that have the async interval attribute specified
- IEnumerable<Tuple<AsyncIntervalAttribute, AsyncSchedulableCallback>> intervals = epType.GetMethods()
- .Where(m => m.GetCustomAttribute<AsyncIntervalAttribute>() != null)
- .Select(m => new Tuple<AsyncIntervalAttribute, AsyncSchedulableCallback>(
- m.GetCustomAttribute<AsyncIntervalAttribute>()!, m.CreateDelegate<AsyncSchedulableCallback>(endpointInstance))
- );
-
- //Schedule event handlers on the current plugin
- foreach (Tuple<AsyncIntervalAttribute, AsyncSchedulableCallback> interval in intervals)
- {
- plugin.ScheduleInterval(interval.Item2, interval.Item1.Interval);
- }
- }
- }
-}
diff --git a/VNLib.Plugins.Extensions.Loading/S3Config.cs b/VNLib.Plugins.Extensions.Loading/S3Config.cs
deleted file mode 100644
index 6d4ae4d..0000000
--- a/VNLib.Plugins.Extensions.Loading/S3Config.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Extensions.Loading
-* File: S3Config.cs
-*
-* S3Config.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 General Public License as published
-* by the Free Software Foundation, either version 2 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
-* General Public License for more details.
-*
-* You should have received a copy of the GNU General Public License
-* along with VNLib.Plugins.Extensions.Loading. If not, see http://www.gnu.org/licenses/.
-*/
-
-using System.Threading.Tasks;
-
-namespace VNLib.Plugins.Extensions.Loading
-{
- public sealed class S3Config
- {
- public string? ServerAddress { get; init; }
- public string? ClientId { get; init; }
- public Task<SecretResult?> ClientSecret { get; init; }
- public string? BaseBucket { get; init; }
- public bool? UseSsl { get; init; }
- public string? Region { get; init; }
-
- public S3Config()
- {
- ClientSecret = Task.FromResult<SecretResult?>(null);
- }
- }
-}
diff --git a/VNLib.Plugins.Extensions.Loading/SecretResult.cs b/VNLib.Plugins.Extensions.Loading/SecretResult.cs
deleted file mode 100644
index 15323f3..0000000
--- a/VNLib.Plugins.Extensions.Loading/SecretResult.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Extensions.Loading
-* File: SecretResult.cs
-*
-* SecretResult.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 General Public License as published
-* by the Free Software Foundation, either version 2 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
-* General Public License for more details.
-*
-* You should have received a copy of the GNU General Public License
-* along with VNLib.Plugins.Extensions.Loading. If not, see http://www.gnu.org/licenses/.
-*/
-
-using System;
-
-using VNLib.Utils;
-using VNLib.Utils.Extensions;
-using VNLib.Utils.Memory;
-
-namespace VNLib.Plugins.Extensions.Loading
-{
- /// <summary>
- /// The result of a secret fetch operation
- /// </summary>
- public sealed class SecretResult : VnDisposeable
- {
- private readonly char[] _secretChars;
-
- /// <summary>
- /// The protected raw result value
- /// </summary>
- public ReadOnlySpan<char> Result => _secretChars;
-
-
- internal SecretResult(ReadOnlySpan<char> value) => _secretChars = value.ToArray();
-
- ///<inheritdoc/>
- protected override void Free()
- {
- Memory.InitializeBlock(_secretChars.AsSpan());
- }
-
- internal static SecretResult ToSecret(string? result)
- {
- SecretResult res = new(result.AsSpan());
- Memory.UnsafeZeroMemory<char>(result);
- return res;
- }
- }
-}
diff --git a/VNLib.Plugins.Extensions.Loading/UserLoading.cs b/VNLib.Plugins.Extensions.Loading/UserLoading.cs
deleted file mode 100644
index da090ec..0000000
--- a/VNLib.Plugins.Extensions.Loading/UserLoading.cs
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Extensions.Loading
-* File: UserLoading.cs
-*
-* UserLoading.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 General Public License as published
-* by the Free Software Foundation, either version 2 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
-* General Public License for more details.
-*
-* You should have received a copy of the GNU General Public License
-* along with VNLib.Plugins.Extensions.Loading. If not, see http://www.gnu.org/licenses/.
-*/
-
-using System;
-using System.Collections.Generic;
-
-using VNLib.Utils.Logging;
-using VNLib.Utils.Extensions;
-using VNLib.Plugins.Essentials.Users;
-
-namespace VNLib.Plugins.Extensions.Loading.Users
-{
- /// <summary>
- /// Contains extension methods for plugins to load the "users" system
- /// </summary>
- public static class UserLoading
- {
- public const string USER_CUSTOM_ASSEMBLY = "user_custom_asm";
- public const string DEFAULT_USER_ASM = "VNLib.Plugins.Essentials.Users.dll";
- public const string ONLOAD_METHOD_NAME = "OnPluginLoading";
-
-
- /// <summary>
- /// Gets or loads the plugin's ambient <see cref="IUserManager"/>, with the specified user-table name,
- /// or the default table name
- /// </summary>
- /// <param name="plugin"></param>
- /// <returns>The ambient <see cref="IUserManager"/> for the current plugin</returns>
- /// <exception cref="KeyNotFoundException"></exception>
- /// <exception cref="ObjectDisposedException"></exception>
- public static IUserManager GetUserManager(this PluginBase plugin)
- {
- plugin.ThrowIfUnloaded();
- //Get stored or load
- return LoadingExtensions.GetOrCreateSingleton(plugin, LoadUsers);
- }
-
- private static IUserManager LoadUsers(PluginBase pbase)
- {
- //Try to load a custom user assembly for exporting IUserManager
- string? customAsm = pbase.PluginConfig.GetPropString(USER_CUSTOM_ASSEMBLY);
- //See if host config defined the path
- customAsm ??= pbase.HostConfig.GetPropString(USER_CUSTOM_ASSEMBLY);
- //Finally default
- customAsm ??= DEFAULT_USER_ASM;
-
- //Try to load a custom assembly
- AssemblyLoader<IUserManager> loader = pbase.LoadAssembly<IUserManager>(customAsm);
- try
- {
- //Try to get the onload method
- Action<object>? onLoadMethod = loader.TryGetMethod<Action<object>>(ONLOAD_METHOD_NAME);
-
- //Call the onplugin load method
- onLoadMethod?.Invoke(pbase);
-
- if (pbase.IsDebug())
- {
- pbase.Log.Verbose("Loading user manager from assembly {name}", loader.Resource.GetType().AssemblyQualifiedName);
- }
-
- //Return the loaded instance (may raise exception)
- return loader.Resource;
- }
- catch
- {
- loader.Dispose();
- throw;
- }
- }
- }
-} \ No newline at end of file
diff --git a/VNLib.Plugins.Extensions.Loading/VNLib.Plugins.Extensions.Loading.csproj b/VNLib.Plugins.Extensions.Loading/VNLib.Plugins.Extensions.Loading.csproj
deleted file mode 100644
index 15bb15e..0000000
--- a/VNLib.Plugins.Extensions.Loading/VNLib.Plugins.Extensions.Loading.csproj
+++ /dev/null
@@ -1,39 +0,0 @@
-<Project Sdk="Microsoft.NET.Sdk">
-
- <PropertyGroup>
- <TargetFramework>net6.0</TargetFramework>
- <Copyright>Copyright © 2022 Vaughn Nugent</Copyright>
- <Authors>Vaughn Nugent</Authors>
- <Version>1.0.1.1</Version>
-
- <GenerateDocumentationFile>True</GenerateDocumentationFile>
- <Nullable>enable</Nullable>
- <SignAssembly>True</SignAssembly>
- <AssemblyOriginatorKeyFile>\\vaughnnugent.com\Internal\Folder Redirection\vman\Documents\Programming\Software\StrongNameingKey.snk</AssemblyOriginatorKeyFile>
- </PropertyGroup>
-
- <PropertyGroup>
- <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
- <PackageProjectUrl>https://www.vaughnnugent.com/resources</PackageProjectUrl>
- <AnalysisLevel>latest-all</AnalysisLevel>
- </PropertyGroup>
-
- <ItemGroup>
- <PackageReference Include="ErrorProne.NET.CoreAnalyzers" Version="0.1.2">
- <PrivateAssets>all</PrivateAssets>
- <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
- </PackageReference>
- <PackageReference Include="ErrorProne.NET.Structs" Version="0.1.2">
- <PrivateAssets>all</PrivateAssets>
- <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
- </PackageReference>
- <PackageReference Include="McMaster.NETCore.Plugins" Version="1.4.0" />
- <PackageReference Include="VaultSharp" Version="1.7.1" />
- </ItemGroup>
-
- <ItemGroup>
- <ProjectReference Include="..\..\..\VNLib\Essentials\src\VNLib.Plugins.Essentials.csproj" />
- <ProjectReference Include="..\..\PluginBase\VNLib.Plugins.PluginBase.csproj" />
- </ItemGroup>
-
-</Project>
diff --git a/VNLib.Plugins.Extensions.Loading/VNLib.Plugins.Extensions.Loading.xml b/VNLib.Plugins.Extensions.Loading/VNLib.Plugins.Extensions.Loading.xml
deleted file mode 100644
index 963f506..0000000
--- a/VNLib.Plugins.Extensions.Loading/VNLib.Plugins.Extensions.Loading.xml
+++ /dev/null
@@ -1,260 +0,0 @@
-<?xml version="1.0"?>
-<!--
-Copyright (c) 2022 Vaughn Nugent
--->
-<doc>
- <assembly>
- <name>VNLib.Plugins.Extensions.Loading</name>
- </assembly>
- <members>
- <member name="T:VNLib.Plugins.Extensions.Loading.Configuration.ConfigurationNameAttribute">
- <summary>
- Specifies a configuration variable name in the plugin's configuration
- containing data specific to the type
- </summary>
- </member>
- <member name="F:VNLib.Plugins.Extensions.Loading.Configuration.ConfigurationNameAttribute.ConfigVarName">
- <summary>
-
- </summary>
- </member>
- <member name="M:VNLib.Plugins.Extensions.Loading.Configuration.ConfigurationNameAttribute.#ctor(System.String)">
- <summary>
- Initializes a new <see cref="T:VNLib.Plugins.Extensions.Loading.Configuration.ConfigurationNameAttribute"/>
- </summary>
- <param name="configVarName">The name of the configuration variable for the class</param>
- </member>
- <member name="T:VNLib.Plugins.Extensions.Loading.Configuration.ConfigurationExtensions">
- <summary>
- Contains extensions for plugin configuration specifc extensions
- </summary>
- </member>
- <member name="M:VNLib.Plugins.Extensions.Loading.Configuration.ConfigurationExtensions.GetConfigForType``1(VNLib.Plugins.PluginBase)">
- <summary>
- Retrieves a top level configuration dictionary of elements for the specified type.
- The type must contain a <see cref="T:VNLib.Plugins.Extensions.Loading.Configuration.ConfigurationNameAttribute"/>
- </summary>
- <typeparam name="T">The type to get the configuration of</typeparam>
- <param name="plugin"></param>
- <returns>A <see cref="T:System.Collections.Generic.Dictionary`2"/> of top level configuration elements for the type</returns>
- <exception cref="T:System.ObjectDisposedException"></exception>
- </member>
- <member name="M:VNLib.Plugins.Extensions.Loading.Configuration.ConfigurationExtensions.GetConfig(VNLib.Plugins.PluginBase,System.String)">
- <summary>
- Retrieves a top level configuration dictionary of elements with the specified property name,
- from the plugin config first, or falls back to the host config file
- </summary>
- <param name="plugin"></param>
- <param name="propName">The config property name to retrieve</param>
- <returns>A <see cref="T:System.Collections.Generic.Dictionary`2"/> of top level configuration elements for the type</returns>
- <exception cref="T:System.ObjectDisposedException"></exception>
- </member>
- <member name="M:VNLib.Plugins.Extensions.Loading.Configuration.ConfigurationExtensions.TryGetConfig(VNLib.Plugins.PluginBase,System.String)">
- <summary>
- Retrieves a top level configuration dictionary of elements with the specified property name,
- from the plugin config first, or falls back to the host config file
- </summary>
- <param name="plugin"></param>
- <param name="propName">The config property name to retrieve</param>
- <returns>A <see cref="T:System.Collections.Generic.Dictionary`2"/> of top level configuration elements for the type</returns>
- <exception cref="T:System.ObjectDisposedException"></exception>
- </member>
- <member name="M:VNLib.Plugins.Extensions.Loading.Configuration.ConfigurationExtensions.GetConfigForType(VNLib.Plugins.PluginBase,System.Type)">
- <summary>
- Retrieves a top level configuration dictionary of elements for the specified type.
- The type must contain a <see cref="T:VNLib.Plugins.Extensions.Loading.Configuration.ConfigurationNameAttribute"/>
- </summary>
- <param name="plugin"></param>
- <param name="type">The type to get configuration data for</param>
- <returns>A <see cref="T:System.Collections.Generic.Dictionary`2"/> of top level configuration elements for the type</returns>
- <exception cref="T:System.ObjectDisposedException"></exception>
- </member>
- <member name="M:VNLib.Plugins.Extensions.Loading.Configuration.ConfigurationExtensions.GetConfig(VNLib.Plugins.PluginBase,System.Object)">
- <summary>
- Shortcut extension for <see cref="M:VNLib.Plugins.Extensions.Loading.Configuration.ConfigurationExtensions.GetConfigForType``1(VNLib.Plugins.PluginBase)"/> to get
- config of current class
- </summary>
- <param name="obj">The object that a configuration can be retrieved for</param>
- <param name="plugin">The plugin containing configuration variables</param>
- <returns>A <see cref="T:System.Collections.Generic.Dictionary`2"/> of top level configuration elements for the type</returns>
- <exception cref="T:System.ObjectDisposedException"></exception>
- </member>
- <member name="M:VNLib.Plugins.Extensions.Loading.Configuration.ConfigurationExtensions.HasConfigForType``1(VNLib.Plugins.PluginBase)">
- <summary>
- Determines if the current plugin configuration contains the require properties to initialize
- the type
- </summary>
- <typeparam name="T"></typeparam>
- <param name="plugin"></param>
- <returns>True if the plugin config contains the require configuration property</returns>
- </member>
- <member name="T:VNLib.Plugins.Extensions.Loading.Events.AsyncIntervalAttribute">
- <summary>
- When added to a method schedules it as a callback on a specified interval when
- the plugin is loaded, and stops when unloaded
- </summary>
- </member>
- <member name="M:VNLib.Plugins.Extensions.Loading.Events.AsyncIntervalAttribute.#ctor(System.Int32)">
- <summary>
- Intializes the <see cref="T:VNLib.Plugins.Extensions.Loading.Events.AsyncIntervalAttribute"/> with the specified timeout in milliseconds
- </summary>
- <param name="milliseconds">The interval in milliseconds</param>
- </member>
- <member name="T:VNLib.Plugins.Extensions.Loading.Events.IntervalResultionType">
- <summary>
- The configurable event interval resulution type
- </summary>
- </member>
- <member name="F:VNLib.Plugins.Extensions.Loading.Events.IntervalResultionType.Milliseconds">
- <summary>
- Specifies event interval resolution in milliseconds
- </summary>
- </member>
- <member name="F:VNLib.Plugins.Extensions.Loading.Events.IntervalResultionType.Seconds">
- <summary>
- Specifies event interval resolution in seconds
- </summary>
- </member>
- <member name="F:VNLib.Plugins.Extensions.Loading.Events.IntervalResultionType.Minutes">
- <summary>
- Specifies event interval resolution in minutes
- </summary>
- </member>
- <member name="F:VNLib.Plugins.Extensions.Loading.Events.IntervalResultionType.Hours">
- <summary>
- Specifies event interval resolution in hours
- </summary>
- </member>
- <member name="T:VNLib.Plugins.Extensions.Loading.Events.ConfigurableAsyncIntervalAttribute">
- <summary>
- When added to a method schedules it as a callback on a specified interval when
- the plugin is loaded, and stops when unloaded
- </summary>
- </member>
- <member name="M:VNLib.Plugins.Extensions.Loading.Events.ConfigurableAsyncIntervalAttribute.#ctor(System.String,VNLib.Plugins.Extensions.Loading.Events.IntervalResultionType)">
- <summary>
- Initializes a <see cref="T:VNLib.Plugins.Extensions.Loading.Events.ConfigurableAsyncIntervalAttribute"/> with the specified
- interval property name
- </summary>
- <param name="configPropName">The configuration property name for the event interval</param>
- <param name="resolution">The time resoltion for the event interval</param>
- </member>
- <member name="T:VNLib.Plugins.Extensions.Loading.Events.EventHandle">
- <summary>
- Represents a handle to a scheduled event interval that is managed by the plugin but may be cancled by disposing the instance
- </summary>
- </member>
- <member name="M:VNLib.Plugins.Extensions.Loading.Events.EventHandle.Pause">
- <summary>
- Pauses the event timer until the <see cref="T:VNLib.Utils.OpenHandle"/> is released or disposed
- then resumes to the inital interval period
- </summary>
- <returns>A <see cref="T:VNLib.Utils.OpenHandle"/> that restores the timer to its initial state when disposed</returns>
- <exception cref="T:System.ObjectDisposedException"></exception>
- </member>
- <member name="M:VNLib.Plugins.Extensions.Loading.Events.EventHandle.Free">
- <inheritdoc/>
- </member>
- <member name="T:VNLib.Plugins.Extensions.Loading.Events.EventManagment">
- <summary>
- Provides event schedueling extensions for plugins
- </summary>
- </member>
- <member name="M:VNLib.Plugins.Extensions.Loading.Events.EventManagment.ScheduleInterval``1(VNLib.Plugins.PluginBase,System.Func{``0,System.Threading.Tasks.Task},``0,System.TimeSpan)">
- <summary>
- Schedules an asynchronous event interval for the current plugin, that is active until canceled or until the plugin unloads
- </summary>
- <typeparam name="TState">Stateful event argument</typeparam>
- <param name="plugin"></param>
- <param name="asyncCallback">An asyncrhonous callback method.</param>
- <param name="state"></param>
- <param name="interval">The event interval</param>
- <returns>An <see cref="T:VNLib.Plugins.Extensions.Loading.Events.EventHandle"/> that can manage the interval state</returns>
- <exception cref="T:System.ObjectDisposedException"></exception>
- <remarks>If exceptions are raised during callback execution, they are written to the plugin's default log provider</remarks>
- </member>
- <member name="T:VNLib.Plugins.Extensions.Loading.LoadingExtensions">
- <summary>
- Provides common loading (and unloading when required) extensions for plugins
- </summary>
- </member>
- <member name="M:VNLib.Plugins.Extensions.Loading.LoadingExtensions.GetPasswords(VNLib.Plugins.PluginBase)">
- <summary>
- Gets the plugins ambient <see cref="T:VNLib.Plugins.Essentials.Accounts.PasswordHashing"/> if loaded, or loads it if required. This class will
- be unloaded when the plugin us unloaded.
- </summary>
- <param name="plugin"></param>
- <returns>The ambient <see cref="T:VNLib.Plugins.Essentials.Accounts.PasswordHashing"/></returns>
- <exception cref="T:System.OverflowException"></exception>
- <exception cref="T:System.Collections.Generic.KeyNotFoundException"></exception>
- <exception cref="T:System.ObjectDisposedException"></exception>
- </member>
- <member name="M:VNLib.Plugins.Extensions.Loading.LoadingExtensions.GetUserManager(VNLib.Plugins.PluginBase)">
- <summary>
- Gets or loads the plugin's ambient <see cref="T:VNLib.Plugins.Essentials.Users.UserManager"/>, with the specified user-table name,
- or the default table name
- </summary>
- <param name="plugin"></param>
- <returns>The ambient <see cref="T:VNLib.Plugins.Essentials.Users.UserManager"/> for the current plugin</returns>
- <exception cref="T:System.Collections.Generic.KeyNotFoundException"></exception>
- <exception cref="T:System.ObjectDisposedException"></exception>
- </member>
- <member name="M:VNLib.Plugins.Extensions.Loading.LoadingExtensions.IsDebug(VNLib.Plugins.PluginBase)">
- <summary>
- Determintes if the current plugin config has a debug propety set
- </summary>
- <param name="plugin"></param>
- <returns>True if debug mode is enabled, false otherwise</returns>
- <exception cref="T:System.ObjectDisposedException"></exception>
- </member>
- <member name="M:VNLib.Plugins.Extensions.Loading.LoadingExtensions.ThrowIfUnloaded(VNLib.Plugins.PluginBase)">
- <summary>
- Internal exception helper to raise <see cref="T:System.ObjectDisposedException"/> if the plugin has been unlaoded
- </summary>
- <param name="plugin"></param>
- <exception cref="T:System.ObjectDisposedException"></exception>
- </member>
- <member name="M:VNLib.Plugins.Extensions.Loading.Routing.RoutingExtensions.Route``1(VNLib.Plugins.PluginBase,System.String)">
- <summary>
- Constructs and routes the specific endpoint type for the current plugin
- </summary>
- <typeparam name="T">The <see cref="T:VNLib.Plugins.IEndpoint"/> type</typeparam>
- <param name="plugin"></param>
- <param name="pluginConfigPathName">The path to the plugin sepcific configuration property</param>
- <exception cref="T:System.Reflection.TargetInvocationException"></exception>
- </member>
- <member name="M:VNLib.Plugins.Extensions.Loading.Routing.RoutingExtensions.Route``1(VNLib.Plugins.PluginBase)">
- <summary>
- Constructs and routes the specific endpoint type for the current plugin
- </summary>
- <typeparam name="T">The <see cref="T:VNLib.Plugins.IEndpoint"/> type</typeparam>
- <param name="plugin"></param>
- <exception cref="T:System.Reflection.TargetInvocationException"></exception>
- </member>
- <member name="T:VNLib.Plugins.Extensions.Loading.Sql.SqlDbConnectionLoader">
- <summary>
- Provides common basic SQL loading extensions for plugins
- </summary>
- </member>
- <member name="M:VNLib.Plugins.Extensions.Loading.Sql.SqlDbConnectionLoader.GetConnectionFactory(VNLib.Plugins.PluginBase)">
- <summary>
- Gets (or loads) the ambient sql connection factory for the current plugin
- </summary>
- <param name="plugin"></param>
- <returns>The ambient <see cref="T:System.Data.Common.DbConnection"/> factory</returns>
- <exception cref="T:System.Collections.Generic.KeyNotFoundException"></exception>
- <exception cref="T:System.ObjectDisposedException"></exception>
- </member>
- <member name="M:VNLib.Plugins.Extensions.Loading.Sql.SqlDbConnectionLoader.GetContextOptions(VNLib.Plugins.PluginBase)">
- <summary>
- Gets (or loads) the ambient <see cref="T:Microsoft.EntityFrameworkCore.DbContextOptions"/> configured from
- the ambient sql factory
- </summary>
- <param name="plugin"></param>
- <returns>The ambient <see cref="T:Microsoft.EntityFrameworkCore.DbContextOptions"/> for the current plugin</returns>
- <exception cref="T:System.Collections.Generic.KeyNotFoundException"></exception>
- <exception cref="T:System.ObjectDisposedException"></exception>
- <remarks>If plugin is in debug mode, writes log data to the default log</remarks>
- </member>
- </members>
-</doc>
diff --git a/VNLib.Plugins.Extensions.Loading/VaultSecrets.cs b/VNLib.Plugins.Extensions.Loading/VaultSecrets.cs
deleted file mode 100644
index cd67903..0000000
--- a/VNLib.Plugins.Extensions.Loading/VaultSecrets.cs
+++ /dev/null
@@ -1,453 +0,0 @@
-/*
-* Copyright (c) 2022 Vaughn Nugent
-*
-* Library: VNLib
-* Package: VNLib.Plugins.Extensions.Loading
-* File: VaultSecrets.cs
-*
-* VaultSecrets.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 General Public License as published
-* by the Free Software Foundation, either version 2 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
-* General Public License for more details.
-*
-* You should have received a copy of the GNU General Public License
-* along with VNLib.Plugins.Extensions.Loading. If not, see http://www.gnu.org/licenses/.
-*/
-
-using System;
-using System.Linq;
-using System.Text;
-using System.Text.Json;
-using System.Threading.Tasks;
-using System.Collections.Generic;
-using System.Security.Cryptography.X509Certificates;
-
-using VaultSharp;
-using VaultSharp.V1.Commons;
-using VaultSharp.V1.AuthMethods;
-using VaultSharp.V1.AuthMethods.Token;
-using VaultSharp.V1.AuthMethods.AppRole;
-using VaultSharp.V1.SecretsEngines.PKI;
-
-using VNLib.Utils;
-using VNLib.Utils.Memory;
-using VNLib.Utils.Logging;
-using VNLib.Utils.Extensions;
-using VNLib.Hashing.IdentityUtility;
-
-namespace VNLib.Plugins.Extensions.Loading
-{
-
- /// <summary>
- /// Adds loading extensions for secure/centralized configuration secrets
- /// </summary>
- public static class PluginSecretLoading
- {
- public const string VAULT_OBJECT_NAME = "hashicorp_vault";
- public const string SECRETS_CONFIG_KEY = "secrets";
- public const string VAULT_TOKEN_KEY = "token";
- public const string VAULT_ROLE_KEY = "role";
- public const string VAULT_SECRET_KEY = "secret";
-
- public const string VAULT_URL_KEY = "url";
-
- public const string VAULT_URL_SCHEME = "vault://";
-
-
- /// <summary>
- /// <para>
- /// Gets a secret from the "secrets" element.
- /// </para>
- /// <para>
- /// Secrets elements are merged from the host config and plugin local config 'secrets' element.
- /// before searching. The plugin config takes precedence over the host config.
- /// </para>
- /// </summary>
- /// <param name="plugin"></param>
- /// <param name="secretName">The name of the secret propery to get</param>
- /// <returns>The element from the configuration file with the given name, or null if the configuration or property does not exist</returns>
- /// <exception cref="KeyNotFoundException"></exception>
- /// <exception cref="ObjectDisposedException"></exception>
- public static Task<SecretResult?> TryGetSecretAsync(this PluginBase plugin, string secretName)
- {
- //Get the secret from the config file raw
- string? rawSecret = TryGetSecretInternal(plugin, secretName);
- if (rawSecret == null)
- {
- return Task.FromResult<SecretResult?>(null);
- }
-
- //Secret is a vault path, or return the raw value
- if (!rawSecret.StartsWith(VAULT_URL_SCHEME, StringComparison.OrdinalIgnoreCase))
- {
- return Task.FromResult<SecretResult?>(new(rawSecret.AsSpan()));
- }
- return GetSecretFromVaultAsync(plugin, rawSecret);
- }
-
- /// <summary>
- /// Gets a secret at the given vault url (in the form of "vault://[mount-name]/[secret-path]?secret=[secret_name]")
- /// </summary>
- /// <param name="plugin"></param>
- /// <param name="vaultPath">The raw vault url to lookup</param>
- /// <returns>The string of the object at the specified vault path</returns>
- /// <exception cref="UriFormatException"></exception>
- /// <exception cref="KeyNotFoundException"></exception>
- /// <exception cref="ObjectDisposedException"></exception>
- public static Task<SecretResult?> GetSecretFromVaultAsync(this PluginBase plugin, ReadOnlySpan<char> vaultPath)
- {
- //print the path for debug
- if (plugin.IsDebug())
- {
- plugin.Log.Debug("Retrieving secret {s} from vault", vaultPath.ToString());
- }
-
- //Slice off path
- ReadOnlySpan<char> paq = vaultPath.SliceAfterParam(VAULT_URL_SCHEME);
- ReadOnlySpan<char> path = paq.SliceBeforeParam('?');
- ReadOnlySpan<char> query = paq.SliceAfterParam('?');
-
- if (paq.IsEmpty)
- {
- throw new UriFormatException("Vault secret location not valid/empty ");
- }
- //Get the secret
- string secretTableKey = query.SliceAfterParam("secret=").SliceBeforeParam('&').ToString();
- string vaultType = query.SliceBeforeParam("vault_type=").SliceBeforeParam('&').ToString();
-
- //get mount and path
- int lastSep = path.IndexOf('/');
- string mount = path[..lastSep].ToString();
- string secret = path[(lastSep + 1)..].ToString();
-
- async Task<SecretResult?> execute()
- {
- //Try load client
- IVaultClient? client = plugin.GetVault();
-
- _ = client ?? throw new KeyNotFoundException("Vault client not found");
- //run read async
- Secret<SecretData> result = await client.V1.Secrets.KeyValue.V2.ReadSecretAsync(path:secret, mountPoint:mount);
- //Read the secret
- return SecretResult.ToSecret(result.Data.Data[secretTableKey].ToString());
- }
-
- return Task.Run(execute);
- }
-
- /// <summary>
- /// <para>
- /// Gets a Certicate from the "secrets" element.
- /// </para>
- /// <para>
- /// Secrets elements are merged from the host config and plugin local config 'secrets' element.
- /// before searching. The plugin config takes precedence over the host config.
- /// </para>
- /// </summary>
- /// <param name="plugin"></param>
- /// <param name="secretName">The name of the secret propery to get</param>
- /// <returns>The element from the configuration file with the given name, or null if the configuration or property does not exist</returns>
- /// <exception cref="KeyNotFoundException"></exception>
- /// <exception cref="ObjectDisposedException"></exception>
- public static Task<X509Certificate?> TryGetCertificateAsync(this PluginBase plugin, string secretName)
- {
- //Get the secret from the config file raw
- string? rawSecret = TryGetSecretInternal(plugin, secretName);
- if (rawSecret == null)
- {
- return Task.FromResult<X509Certificate?>(null);
- }
-
- //Secret is a vault path, or return the raw value
- if (!rawSecret.StartsWith(VAULT_URL_SCHEME, StringComparison.OrdinalIgnoreCase))
- {
- return Task.FromResult<X509Certificate?>(new (rawSecret));
- }
- return GetCertFromVaultAsync(plugin, rawSecret);
- }
-
- public static Task<X509Certificate?> GetCertFromVaultAsync(this PluginBase plugin, ReadOnlySpan<char> vaultPath, CertificateCredentialsRequestOptions? options = null)
- {
- //print the path for debug
- if (plugin.IsDebug())
- {
- plugin.Log.Debug("Retrieving certificate {s} from vault", vaultPath.ToString());
- }
-
- //Slice off path
- ReadOnlySpan<char> paq = vaultPath.SliceAfterParam(VAULT_URL_SCHEME);
- ReadOnlySpan<char> path = paq.SliceBeforeParam('?');
- ReadOnlySpan<char> query = paq.SliceAfterParam('?');
-
- if (paq.IsEmpty)
- {
- throw new UriFormatException("Vault secret location not valid/empty ");
- }
-
- //Get the secret
- string role = query.SliceAfterParam("role=").SliceBeforeParam('&').ToString();
- string vaultType = query.SliceBeforeParam("vault_type=").SliceBeforeParam('&').ToString();
- string commonName = query.SliceBeforeParam("cn=").SliceBeforeParam('&').ToString();
-
- //get mount and path
- int lastSep = path.IndexOf('/');
- string mount = path[..lastSep].ToString();
- string secret = path[(lastSep + 1)..].ToString();
-
- async Task<X509Certificate?> execute()
- {
- //Try load client
- IVaultClient? client = plugin.GetVault();
-
- _ = client ?? throw new KeyNotFoundException("Vault client not found");
-
- options ??= new()
- {
- CertificateFormat = CertificateFormat.pem,
- PrivateKeyFormat = PrivateKeyFormat.pkcs8,
- CommonName = commonName,
- };
-
- //run read async
- Secret<CertificateCredentials> result = await client.V1.Secrets.PKI.GetCredentialsAsync(pkiRoleName:secret, certificateCredentialRequestOptions:options, pkiBackendMountPoint:mount);
- //Read the secret
- byte[] pemCertData = Encoding.UTF8.GetBytes(result.Data.CertificateContent);
-
- return new (pemCertData);
- }
-
- return Task.Run(execute);
- }
-
- /// <summary>
- /// Gets the ambient vault client for the current plugin
- /// if the configuration is loaded, null otherwise
- /// </summary>
- /// <param name="plugin"></param>
- /// <returns>The ambient <see cref="IVaultClient"/> if loaded, null otherwise</returns>
- /// <exception cref="KeyNotFoundException"></exception>
- /// <exception cref="ObjectDisposedException"></exception>
- public static IVaultClient? GetVault(this PluginBase plugin) => LoadingExtensions.GetOrCreateSingleton(plugin, TryGetVaultLoader);
-
- private static string? TryGetSecretInternal(PluginBase plugin, string secretName)
- {
- bool local = plugin.PluginConfig.TryGetProperty(SECRETS_CONFIG_KEY, out JsonElement localEl);
- bool host = plugin.HostConfig.TryGetProperty(SECRETS_CONFIG_KEY, out JsonElement hostEl);
-
- //total config
- IReadOnlyDictionary<string, JsonElement>? conf;
-
- if (local && host)
- {
- //Load both config objects to dict
- Dictionary<string, JsonElement> localConf = localEl.EnumerateObject().ToDictionary(x => x.Name, x => x.Value);
- Dictionary<string, JsonElement> hostConf = hostEl.EnumerateObject().ToDictionary(x => x.Name, x => x.Value);
-
- //merge the two configs
- foreach(KeyValuePair<string, JsonElement> lc in localConf)
- {
- //Overwrite any host config keys, plugin conf takes priority
- hostConf[lc.Key] = lc.Value;
- }
- //set the merged config
- conf = hostConf;
- }
- else if(local)
- {
- //Store only local config
- conf = localEl.EnumerateObject().ToDictionary(x => x.Name, x => x.Value);
- }
- else if(host)
- {
- //store only host config
- conf = hostEl.EnumerateObject().ToDictionary(x => x.Name, x => x.Value);
- }
- else
- {
- conf = null;
- }
-
- //Get the value or default json element
- return conf != null && conf.TryGetValue(secretName, out JsonElement el) ? el.GetString() : null;
- }
-
- private static IVaultClient? TryGetVaultLoader(PluginBase pbase)
- {
- //Get vault config
- IReadOnlyDictionary<string, JsonElement>? conf = pbase.TryGetConfig(VAULT_OBJECT_NAME);
-
- if (conf == null)
- {
- return null;
- }
-
- //try get servre address creds from config
- string? serverAddress = conf[VAULT_URL_KEY].GetString() ?? throw new KeyNotFoundException($"Failed to load the key {VAULT_URL_KEY} from object {VAULT_OBJECT_NAME}");
-
- IAuthMethodInfo authMethod;
-
- //Get authentication method from config
- if (conf.TryGetValue(VAULT_TOKEN_KEY, out JsonElement tokenEl))
- {
- //Init token
- authMethod = new TokenAuthMethodInfo(tokenEl.GetString());
- }
- else if (conf.TryGetValue(VAULT_ROLE_KEY, out JsonElement roleEl) && conf.TryGetValue(VAULT_SECRET_KEY, out JsonElement secretEl))
- {
- authMethod = new AppRoleAuthMethodInfo(roleEl.GetString(), secretEl.GetString());
- }
- else
- {
- throw new KeyNotFoundException($"Failed to load the vault authentication method from {VAULT_OBJECT_NAME}");
- }
-
- //Settings
- VaultClientSettings settings = new(serverAddress, authMethod);
-
- //create vault client
- return new VaultClient(settings);
- }
-
- /// <summary>
- /// Gets the Secret value as a byte buffer
- /// </summary>
- /// <param name="secret"></param>
- /// <returns>The base64 decoded secret as a byte[]</returns>
- /// <exception cref="ArgumentNullException"></exception>
- /// <exception cref="InternalBufferTooSmallException"></exception>
- public static byte[] GetFromBase64(this SecretResult secret)
- {
- _ = secret ?? throw new ArgumentNullException(nameof(secret));
-
- //Temp buffer
- using UnsafeMemoryHandle<byte> buffer = Memory.UnsafeAlloc<byte>(secret.Result.Length);
-
- //Get base64
- if(Convert.TryFromBase64Chars(secret.Result, buffer, out int count))
- {
- //Copy to array
- byte[] value = buffer.Span[..count].ToArray();
- //Clear block before returning
- Memory.InitializeBlock<byte>(buffer);
-
- return value;
- }
-
- throw new InternalBufferTooSmallException("internal buffer too small");
- }
-
- /// <summary>
- /// Converts the secret recovery task to
- /// </summary>
- /// <param name="secret"></param>
- /// <returns>A task whos result the base64 decoded secret as a byte[]</returns>
- /// <exception cref="ArgumentNullException"></exception>
- /// <exception cref="InternalBufferTooSmallException"></exception>
- public static async Task<byte[]?> ToBase64Bytes(this Task<SecretResult?> secret)
- {
- _ = secret ?? throw new ArgumentNullException(nameof(secret));
- using SecretResult? sec = await secret.ConfigureAwait(false);
- return sec?.GetFromBase64();
- }
-
- /// <summary>
- /// Recovers a certificate from a PEM encoded secret
- /// </summary>
- /// <param name="secret"></param>
- /// <returns>The <see cref="X509Certificate2"/> parsed from the PEM encoded data</returns>
- /// <exception cref="ArgumentNullException"></exception>
- public static X509Certificate2 GetCertificate(this SecretResult secret)
- {
- _ = secret ?? throw new ArgumentNullException(nameof(secret));
- return X509Certificate2.CreateFromPem(secret.Result);
- }
-
- /// <summary>
- /// Gets the secret value as a secret result
- /// </summary>
- /// <param name="secret"></param>
- /// <returns>The document parsed from the secret value</returns>
- public static JsonDocument GetJsonDocument(this SecretResult secret)
- {
- _ = secret ?? throw new ArgumentNullException(nameof(secret));
- //Alloc buffer, utf8 so 1 byte per char
- using IMemoryHandle<byte> buffer = Memory.SafeAlloc<byte>(secret.Result.Length);
- //Get utf8 bytes
- int count = Encoding.UTF8.GetBytes(secret.Result, buffer.Span);
- //Reader and parse
- Utf8JsonReader reader = new(buffer.Span[..count]);
- return JsonDocument.ParseValue(ref reader);
- }
-
- /// <summary>
- /// Gets a SPKI encoded public key from a secret
- /// </summary>
- /// <param name="secret"></param>
- /// <returns>The <see cref="PublicKey"/> parsed from the SPKI public key</returns>
- /// <exception cref="ArgumentNullException"></exception>
- public static PublicKey GetPublicKey(this SecretResult secret)
- {
- _ = secret ?? throw new ArgumentNullException(nameof(secret));
- //Alloc buffer, base64 is larger than binary value so char len is large enough
- using IMemoryHandle<byte> buffer = Memory.SafeAlloc<byte>(secret.Result.Length);
- //Get base64 bytes
- ERRNO count = VnEncoding.TryFromBase64Chars(secret.Result, buffer.Span);
- //Parse the SPKI from base64
- return PublicKey.CreateFromSubjectPublicKeyInfo(buffer.Span[..(int)count], out _);
- }
-
- /// <summary>
- /// Gets the value of the <see cref="SecretResult"/> as a <see cref="PrivateKey"/>
- /// container
- /// </summary>
- /// <param name="secret"></param>
- /// <returns>The <see cref="PrivateKey"/> from the secret value</returns>
- /// <exception cref="FormatException"></exception>
- /// <exception cref="ArgumentNullException"></exception>
- public static PrivateKey GetPrivateKey(this SecretResult secret)
- {
- _ = secret ?? throw new ArgumentNullException(nameof(secret));
- return new PrivateKey(secret);
- }
-
- /// <summary>
- /// Gets a <see cref="ReadOnlyJsonWebKey"/> from a secret value
- /// </summary>
- /// <param name="secret"></param>
- /// <returns>The <see cref="ReadOnlyJsonWebKey"/> from the result</returns>
- /// <exception cref="JsonException"></exception>
- /// <exception cref="ArgumentException"></exception>
- /// <exception cref="ArgumentNullException"></exception>
- public static ReadOnlyJsonWebKey GetJsonWebKey(this SecretResult secret)
- {
- _ = secret ?? throw new ArgumentNullException(nameof(secret));
- //Alloc buffer, utf8 so 1 byte per char
- using IMemoryHandle<byte> buffer = Memory.SafeAlloc<byte>(secret.Result.Length);
- //Get utf8 bytes
- int count = Encoding.UTF8.GetBytes(secret.Result, buffer.Span);
- return new ReadOnlyJsonWebKey(buffer.Span[..count]);
- }
-
- /// <summary>
- /// Gets a task that resolves a <see cref="ReadOnlyJsonWebKey"/>
- /// from a <see cref="SecretResult"/> task
- /// </summary>
- /// <param name="secret"></param>
- /// <returns>The <see cref="ReadOnlyJsonWebKey"/> from the secret, or null if the secret was not found</returns>
- /// <exception cref="ArgumentNullException"></exception>
- public static async Task<ReadOnlyJsonWebKey?> ToJsonWebKey(this Task<SecretResult?> secret)
- {
- _ = secret ?? throw new ArgumentNullException(nameof(secret));
- using SecretResult? sec = await secret.ConfigureAwait(false);
- return sec?.GetJsonWebKey();
- }
- }
-}