Renamed the plugin root of enterprise configurations to say what it holds

This commit is contained in:
Thorsten Sommer 2026-08-08 16:49:57 +02:00
parent 357eadd12d
commit d4f0c650fc
Signed by untrusted user who does not match committer: tsommer
GPG Key ID: 371BBA77A02C0108
6 changed files with 65 additions and 53 deletions

View File

@ -269,12 +269,13 @@ public static partial class ManagedConfiguration
/// change it at all. /// change it at all.
/// </remarks> /// </remarks>
/// <param name="availablePlugins">The collection of available plugins to check against.</param> /// <param name="availablePlugins">The collection of available plugins to check against.</param>
/// <param name="deployedConfigPluginIds"> /// <param name="deployedEnterpriseConfigPluginIds">
/// The IDs of all configuration plugins which are deployed on this machine, including those which /// The IDs of the configuration plugins which an organization deployed on this machine, including
/// could not be loaded. A deployed plugin was not removed, so its settings must stay untouched. /// those which could not be loaded. A deployed plugin was not removed, so its settings must stay
/// untouched.
/// </param> /// </param>
/// <returns>True when at least one setting was changed, otherwise false.</returns> /// <returns>True when at least one setting was changed, otherwise false.</returns>
public static bool CleanupLeftOverManagedConfigurations(IReadOnlyCollection<IAvailablePlugin> availablePlugins, IReadOnlySet<Guid> deployedConfigPluginIds) public static bool CleanupLeftOverManagedConfigurations(IReadOnlyCollection<IAvailablePlugin> availablePlugins, IReadOnlySet<Guid> deployedEnterpriseConfigPluginIds)
{ {
var wasChanged = false; var wasChanged = false;
var registeredSettingNames = new HashSet<string>(StringComparer.Ordinal); var registeredSettingNames = new HashSet<string>(StringComparer.Ordinal);
@ -293,7 +294,7 @@ public static partial class ManagedConfiguration
configMeta.RestoreLockedConfiguration(); configMeta.RestoreLockedConfiguration();
// Check the locked state: // Check the locked state:
if (configMeta.IsLocked && configMeta.LockedByConfigPluginId != Guid.Empty && !IsPluginPresent(configMeta.LockedByConfigPluginId, availablePlugins, deployedConfigPluginIds)) if (configMeta.IsLocked && configMeta.LockedByConfigPluginId != Guid.Empty && !IsPluginPresent(configMeta.LockedByConfigPluginId, availablePlugins, deployedEnterpriseConfigPluginIds))
{ {
Log.LogInformation($"Resetting the setting '{configMeta.SettingName}': it was locked by the configuration plugin '{configMeta.LockedByConfigPluginId}', which is not available anymore."); Log.LogInformation($"Resetting the setting '{configMeta.SettingName}': it was locked by the configuration plugin '{configMeta.LockedByConfigPluginId}', which is not available anymore.");
configMeta.ResetLockedConfiguration(); configMeta.ResetLockedConfiguration();
@ -301,11 +302,11 @@ public static partial class ManagedConfiguration
} }
// Check the editable default state: // Check the editable default state:
if (CleanupEditableDefaultState(configMeta, availablePlugins, deployedConfigPluginIds)) if (CleanupEditableDefaultState(configMeta, availablePlugins, deployedEnterpriseConfigPluginIds))
wasChanged = true; wasChanged = true;
// Check the additive plugin contribution: // Check the additive plugin contribution:
if (configMeta.HasPluginContribution && configMeta.PluginContributionByConfigPluginId != Guid.Empty && !IsPluginPresent(configMeta.PluginContributionByConfigPluginId, availablePlugins, deployedConfigPluginIds)) if (configMeta.HasPluginContribution && configMeta.PluginContributionByConfigPluginId != Guid.Empty && !IsPluginPresent(configMeta.PluginContributionByConfigPluginId, availablePlugins, deployedEnterpriseConfigPluginIds))
{ {
Log.LogInformation($"Clearing the plugin contribution for the setting '{configMeta.SettingName}': the configuration plugin '{configMeta.PluginContributionByConfigPluginId}' is not available anymore."); Log.LogInformation($"Clearing the plugin contribution for the setting '{configMeta.SettingName}': the configuration plugin '{configMeta.PluginContributionByConfigPluginId}' is not available anymore.");
configMeta.ClearPluginContribution(); configMeta.ClearPluginContribution();
@ -328,7 +329,7 @@ public static partial class ManagedConfiguration
/// The latter matters for organizations: a broken configuration plugin is still in charge, so we /// The latter matters for organizations: a broken configuration plugin is still in charge, so we
/// must not treat its settings as left over. /// must not treat its settings as left over.
/// </remarks> /// </remarks>
private static bool IsPluginPresent(Guid configPluginId, IReadOnlyCollection<IAvailablePlugin> availablePlugins, IReadOnlySet<Guid> deployedConfigPluginIds) => deployedConfigPluginIds.Contains(configPluginId) || availablePlugins.Any(x => x.Id == configPluginId); private static bool IsPluginPresent(Guid configPluginId, IReadOnlyCollection<IAvailablePlugin> availablePlugins, IReadOnlySet<Guid> deployedEnterpriseConfigPluginIds) => deployedEnterpriseConfigPluginIds.Contains(configPluginId) || availablePlugins.Any(x => x.Id == configPluginId);
/// <summary> /// <summary>
/// Removes persisted managed states which belong to settings that are not registered anymore. /// Removes persisted managed states which belong to settings that are not registered anymore.
@ -390,7 +391,7 @@ public static partial class ManagedConfiguration
private static bool ClearEditableDefaultState(string settingName) => SettingsManagerAccess.ConfigurationData.ManagedEditableDefaults.Remove(settingName); private static bool ClearEditableDefaultState(string settingName) => SettingsManagerAccess.ConfigurationData.ManagedEditableDefaults.Remove(settingName);
private static bool CleanupEditableDefaultState(ConfigMetaBase configMeta, IReadOnlyCollection<IAvailablePlugin> availablePlugins, IReadOnlySet<Guid> deployedConfigPluginIds) private static bool CleanupEditableDefaultState(ConfigMetaBase configMeta, IReadOnlyCollection<IAvailablePlugin> availablePlugins, IReadOnlySet<Guid> deployedEnterpriseConfigPluginIds)
{ {
if (!TryGetEditableDefaultState(configMeta.SettingName, out var editableDefaultState)) if (!TryGetEditableDefaultState(configMeta.SettingName, out var editableDefaultState))
{ {
@ -401,7 +402,7 @@ public static partial class ManagedConfiguration
return true; return true;
} }
if (IsPluginPresent(editableDefaultState.ConfigPluginId, availablePlugins, deployedConfigPluginIds)) if (IsPluginPresent(editableDefaultState.ConfigPluginId, availablePlugins, deployedEnterpriseConfigPluginIds))
return false; return false;
Log.LogInformation($"Clearing the editable default of the setting '{configMeta.SettingName}': the configuration plugin '{editableDefaultState.ConfigPluginId}' is not available anymore."); Log.LogInformation($"Clearing the editable default of the setting '{configMeta.SettingName}': the configuration plugin '{editableDefaultState.ConfigPluginId}' is not available anymore.");

View File

@ -255,10 +255,10 @@ public sealed record PluginConfigurationObject
/// <param name="configObjectType">The type of configuration object to process.</param> /// <param name="configObjectType">The type of configuration object to process.</param>
/// <param name="configObjectSelection">A selection expression to retrieve the configuration objects from the main configuration.</param> /// <param name="configObjectSelection">A selection expression to retrieve the configuration objects from the main configuration.</param>
/// <param name="availablePlugins">A list of currently available plugins.</param> /// <param name="availablePlugins">A list of currently available plugins.</param>
/// <param name="deployedConfigPluginIds"> /// <param name="deployedEnterpriseConfigPluginIds">
/// The IDs of all configuration plugins which are deployed on this machine, including those which /// The IDs of the configuration plugins which an organization deployed on this machine, including
/// could not be loaded. Objects of a deployed plugin are never removed, because the plugin was not /// those which could not be loaded. Objects of a deployed plugin are never removed, because the
/// removed either. /// plugin was not removed either.
/// </param> /// </param>
/// <param name="configObjectList">A list of all existing configuration objects.</param> /// <param name="configObjectList">A list of all existing configuration objects.</param>
/// <param name="secretStoreType">An optional parameter specifying the type of secret store to use for deleting associated API keys from the OS keyring, if applicable.</param> /// <param name="secretStoreType">An optional parameter specifying the type of secret store to use for deleting associated API keys from the OS keyring, if applicable.</param>
@ -268,7 +268,7 @@ public sealed record PluginConfigurationObject
PluginConfigurationObjectType configObjectType, PluginConfigurationObjectType configObjectType,
Expression<Func<Data, List<TClass>>> configObjectSelection, Expression<Func<Data, List<TClass>>> configObjectSelection,
IList<IAvailablePlugin> availablePlugins, IList<IAvailablePlugin> availablePlugins,
IReadOnlySet<Guid> deployedConfigPluginIds, IReadOnlySet<Guid> deployedEnterpriseConfigPluginIds,
IList<PluginConfigurationObject> configObjectList, IList<PluginConfigurationObject> configObjectList,
SecretStoreType? secretStoreType = null, SecretStoreType? secretStoreType = null,
bool deleteSecret = false) where TClass : IConfigurationObject bool deleteSecret = false) where TClass : IConfigurationObject
@ -295,7 +295,7 @@ public sealed record PluginConfigurationObject
// the organization's providers and data sources, including their secrets, although the // the organization's providers and data sources, including their secrets, although the
// organization still manages this AI Studio instance: // organization still manages this AI Studio instance:
// //
if(deployedConfigPluginIds.Contains(configObjectSourcePluginId) && availablePlugins.All(plugin => plugin.Id != configObjectSourcePluginId)) if(deployedEnterpriseConfigPluginIds.Contains(configObjectSourcePluginId) && availablePlugins.All(plugin => plugin.Id != configObjectSourcePluginId))
continue; continue;
// Is the source plugin still available? If not, we can be pretty sure that this configuration object is left // Is the source plugin still available? If not, we can be pretty sure that this configuration object is left

View File

@ -45,7 +45,7 @@ public static partial class PluginFactory
LOG.LogInformation($"Try to download configuration plugin with ID='{configPlugId}' from server='{configServerUrl}' (GET {downloadUrl})"); LOG.LogInformation($"Try to download configuration plugin with ID='{configPlugId}' from server='{configServerUrl}' (GET {downloadUrl})");
var tempDownloadFile = Path.GetTempFileName(); var tempDownloadFile = Path.GetTempFileName();
var stagedDirectory = Path.Join(CONFIGURATION_PLUGINS_ROOT, $"{configPlugId}.staging-{Guid.NewGuid():N}"); var stagedDirectory = Path.Join(ENTERPRISE_CONFIGURATION_PLUGINS_ROOT, $"{configPlugId}.staging-{Guid.NewGuid():N}");
string? backupDirectory = null; string? backupDirectory = null;
var wasSuccessful = false; var wasSuccessful = false;
try try
@ -66,10 +66,10 @@ public static partial class PluginFactory
ExtractConfigPluginArchive(tempDownloadFile, stagedDirectory); ExtractConfigPluginArchive(tempDownloadFile, stagedDirectory);
var configDirectory = Path.Join(CONFIGURATION_PLUGINS_ROOT, configPlugId.ToString()); var configDirectory = Path.Join(ENTERPRISE_CONFIGURATION_PLUGINS_ROOT, configPlugId.ToString());
if (Directory.Exists(configDirectory)) if (Directory.Exists(configDirectory))
{ {
backupDirectory = Path.Join(CONFIGURATION_PLUGINS_ROOT, $"{configPlugId}.backup-{Guid.NewGuid():N}"); backupDirectory = Path.Join(ENTERPRISE_CONFIGURATION_PLUGINS_ROOT, $"{configPlugId}.backup-{Guid.NewGuid():N}");
Directory.Move(configDirectory, backupDirectory); Directory.Move(configDirectory, backupDirectory);
} }
@ -84,7 +84,7 @@ public static partial class PluginFactory
{ {
LOG.LogError(e, "An error occurred while downloading or extracting the enterprise configuration plugin."); LOG.LogError(e, "An error occurred while downloading or extracting the enterprise configuration plugin.");
var configDirectory = Path.Join(CONFIGURATION_PLUGINS_ROOT, configPlugId.ToString()); var configDirectory = Path.Join(ENTERPRISE_CONFIGURATION_PLUGINS_ROOT, configPlugId.ToString());
if (!string.IsNullOrWhiteSpace(backupDirectory) && Directory.Exists(backupDirectory) && !Directory.Exists(configDirectory)) if (!string.IsNullOrWhiteSpace(backupDirectory) && Directory.Exists(backupDirectory) && !Directory.Exists(configDirectory))
{ {
try try

View File

@ -140,7 +140,7 @@ public static partial class PluginFactory
else if (isConfigurationPluginInConfigDirectory) else if (isConfigurationPluginInConfigDirectory)
{ {
isManagedByConfigServer = true; isManagedByConfigServer = true;
LOG.LogWarning($"The configuration plugin '{plugin.Id}' does not define 'DEPLOYED_USING_CONFIG_SERVER'. Falling back to the plugin path and treating it as managed because it is stored under '{CONFIGURATION_PLUGINS_ROOT}'."); LOG.LogWarning($"The configuration plugin '{plugin.Id}' does not define 'DEPLOYED_USING_CONFIG_SERVER'. Falling back to the plugin path and treating it as managed because it is stored under '{ENTERPRISE_CONFIGURATION_PLUGINS_ROOT}'.");
} }
} }
else if (plugin is PluginAssistants assistantPlugin) else if (plugin is PluginAssistants assistantPlugin)
@ -192,40 +192,40 @@ public static partial class PluginFactory
// //
// //
// Configuration plugins which are deployed but could not be loaded count as present: they // Enterprise configuration plugins which are deployed but could not be loaded count as
// were not removed, so everything they manage must stay as it is. Otherwise, one broken // present: they were not removed, so everything they manage must stay as it is. Otherwise,
// configuration plugin would wipe the entire organization configuration: // one broken configuration plugin would wipe the entire organization configuration:
// //
var deployedConfigPluginIds = GetDeployedConfigPluginIds(); var deployedEnterpriseConfigPluginIds = GetDeployedEnterpriseConfigPluginIds();
var unloadedConfigPluginIds = deployedConfigPluginIds.Where(x => AVAILABLE_PLUGINS.All(plugin => plugin.Id != x)).ToList(); var unloadedEnterpriseConfigPluginIds = deployedEnterpriseConfigPluginIds.Where(x => AVAILABLE_PLUGINS.All(plugin => plugin.Id != x)).ToList();
foreach (var unloadedConfigPluginId in unloadedConfigPluginIds) foreach (var unloadedEnterpriseConfigPluginId in unloadedEnterpriseConfigPluginIds)
LOG.LogWarning($"The configuration plugin '{unloadedConfigPluginId}' is deployed, but was not loaded. Everything it manages stays unchanged, because the plugin was not removed. Please check the errors above and fix the plugin."); LOG.LogWarning($"The configuration plugin '{unloadedEnterpriseConfigPluginId}' is deployed, but was not loaded. Everything it manages stays unchanged, because the plugin was not removed. Please check the errors above and fix the plugin.");
// Check LLM providers: // Check LLM providers:
var wasConfigurationChanged = await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.LLM_PROVIDER, x => x.Providers, AVAILABLE_PLUGINS, deployedConfigPluginIds, configObjectList, SecretStoreType.LLM_PROVIDER); var wasConfigurationChanged = await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.LLM_PROVIDER, x => x.Providers, AVAILABLE_PLUGINS, deployedEnterpriseConfigPluginIds, configObjectList, SecretStoreType.LLM_PROVIDER);
// Check transcription providers: // Check transcription providers:
if(await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.TRANSCRIPTION_PROVIDER, x => x.TranscriptionProviders, AVAILABLE_PLUGINS, deployedConfigPluginIds, configObjectList, SecretStoreType.TRANSCRIPTION_PROVIDER)) if(await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.TRANSCRIPTION_PROVIDER, x => x.TranscriptionProviders, AVAILABLE_PLUGINS, deployedEnterpriseConfigPluginIds, configObjectList, SecretStoreType.TRANSCRIPTION_PROVIDER))
wasConfigurationChanged = true; wasConfigurationChanged = true;
// Check embedding providers: // Check embedding providers:
if(await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.EMBEDDING_PROVIDER, x => x.EmbeddingProviders, AVAILABLE_PLUGINS, deployedConfigPluginIds, configObjectList, SecretStoreType.EMBEDDING_PROVIDER)) if(await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.EMBEDDING_PROVIDER, x => x.EmbeddingProviders, AVAILABLE_PLUGINS, deployedEnterpriseConfigPluginIds, configObjectList, SecretStoreType.EMBEDDING_PROVIDER))
wasConfigurationChanged = true; wasConfigurationChanged = true;
// Check data sources: // Check data sources:
if(await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.DATA_SOURCE, x => x.DataSources, AVAILABLE_PLUGINS, deployedConfigPluginIds, configObjectList, SecretStoreType.DATA_SOURCE, deleteSecret: true)) if(await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.DATA_SOURCE, x => x.DataSources, AVAILABLE_PLUGINS, deployedEnterpriseConfigPluginIds, configObjectList, SecretStoreType.DATA_SOURCE, deleteSecret: true))
wasConfigurationChanged = true; wasConfigurationChanged = true;
// Check chat templates: // Check chat templates:
if(await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.CHAT_TEMPLATE, x => x.ChatTemplates, AVAILABLE_PLUGINS, deployedConfigPluginIds, configObjectList)) if(await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.CHAT_TEMPLATE, x => x.ChatTemplates, AVAILABLE_PLUGINS, deployedEnterpriseConfigPluginIds, configObjectList))
wasConfigurationChanged = true; wasConfigurationChanged = true;
// Check profiles: // Check profiles:
if(await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.PROFILE, x => x.Profiles, AVAILABLE_PLUGINS, deployedConfigPluginIds, configObjectList)) if(await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.PROFILE, x => x.Profiles, AVAILABLE_PLUGINS, deployedEnterpriseConfigPluginIds, configObjectList))
wasConfigurationChanged = true; wasConfigurationChanged = true;
// Check document analysis policies: // Check document analysis policies:
if(await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.DOCUMENT_ANALYSIS_POLICY, x => x.DocumentAnalysis.Policies, AVAILABLE_PLUGINS, deployedConfigPluginIds, configObjectList)) if(await PluginConfigurationObject.CleanLeftOverConfigurationObjects(PluginConfigurationObjectType.DOCUMENT_ANALYSIS_POLICY, x => x.DocumentAnalysis.Policies, AVAILABLE_PLUGINS, deployedEnterpriseConfigPluginIds, configObjectList))
wasConfigurationChanged = true; wasConfigurationChanged = true;
// Check left-over mandatory info acceptances: // Check left-over mandatory info acceptances:
@ -234,11 +234,11 @@ public static partial class PluginFactory
// Check all managed settings, i.e. settings which a configuration plugin can lock, // Check all managed settings, i.e. settings which a configuration plugin can lock,
// provide as an editable default, or contribute to: // provide as an editable default, or contribute to:
if(ManagedConfiguration.CleanupLeftOverManagedConfigurations(AVAILABLE_PLUGINS, deployedConfigPluginIds)) if(ManagedConfiguration.CleanupLeftOverManagedConfigurations(AVAILABLE_PLUGINS, deployedEnterpriseConfigPluginIds))
wasConfigurationChanged = true; wasConfigurationChanged = true;
// Compatibility shim, see documentation/compatibility-shims/2026-08-orphaned-config-locks.md (remove after 2027-08-06): // Compatibility shim, see documentation/compatibility-shims/2026-08-orphaned-config-locks.md (remove after 2027-08-06):
if (RepairLegacyConfigOnlySettings(unloadedConfigPluginIds.Count > 0)) if (RepairLegacyConfigOnlySettings(unloadedEnterpriseConfigPluginIds.Count > 0))
wasConfigurationChanged = true; wasConfigurationChanged = true;
if (wasConfigurationChanged) if (wasConfigurationChanged)
@ -249,9 +249,11 @@ public static partial class PluginFactory
} }
/// <summary> /// <summary>
/// Determines the IDs of all configuration plugins which are deployed on this machine. /// Determines the IDs of all configuration plugins which an organization deployed on this machine.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Local configuration plugins are not part of this: they belong to the user, not to an
/// organization, and they can live in any directory below the plugins root.<br/><br/>
/// We read these IDs from the file system instead of taking them from the loaded plugins. A /// We read these IDs from the file system instead of taking them from the loaded plugins. A
/// configuration plugin might be present but not loadable, e.g. due to invalid Lua code, a /// configuration plugin might be present but not loadable, e.g. due to invalid Lua code, a
/// missing `plugin.lua`, or an incomplete download. Such a plugin still manages this AI Studio /// missing `plugin.lua`, or an incomplete download. Such a plugin still manages this AI Studio
@ -259,13 +261,13 @@ public static partial class PluginFactory
/// configuration server live in a directory named after their ID, which is the only information /// configuration server live in a directory named after their ID, which is the only information
/// left when the plugin itself cannot be read. /// left when the plugin itself cannot be read.
/// </remarks> /// </remarks>
private static HashSet<Guid> GetDeployedConfigPluginIds() private static HashSet<Guid> GetDeployedEnterpriseConfigPluginIds()
{ {
var deployedConfigPluginIds = new HashSet<Guid>(); var deployedEnterpriseConfigPluginIds = new HashSet<Guid>();
if (!Directory.Exists(CONFIGURATION_PLUGINS_ROOT)) if (!Directory.Exists(ENTERPRISE_CONFIGURATION_PLUGINS_ROOT))
return deployedConfigPluginIds; return deployedEnterpriseConfigPluginIds;
foreach (var configPluginDirectory in Directory.EnumerateDirectories(CONFIGURATION_PLUGINS_ROOT)) foreach (var configPluginDirectory in Directory.EnumerateDirectories(ENTERPRISE_CONFIGURATION_PLUGINS_ROOT))
{ {
if (!Guid.TryParse(Path.GetFileName(configPluginDirectory), out var configPluginId) || configPluginId == Guid.Empty) if (!Guid.TryParse(Path.GetFileName(configPluginDirectory), out var configPluginId) || configPluginId == Guid.Empty)
continue; continue;
@ -274,10 +276,10 @@ public static partial class PluginFactory
if (!Directory.EnumerateFileSystemEntries(configPluginDirectory).Any()) if (!Directory.EnumerateFileSystemEntries(configPluginDirectory).Any())
continue; continue;
deployedConfigPluginIds.Add(configPluginId); deployedEnterpriseConfigPluginIds.Add(configPluginId);
} }
return deployedConfigPluginIds; return deployedEnterpriseConfigPluginIds;
} }
/// <param name="pluginPath">The directory the plugin is located in, or null when the code has no directory yet.</param> /// <param name="pluginPath">The directory the plugin is located in, or null when the code has no directory yet.</param>

View File

@ -22,9 +22,9 @@ public static partial class PluginFactory
// Case 2: Startup cleanup before the initial plugin load. // Case 2: Startup cleanup before the initial plugin load.
// In this case, we inspect the .config directories directly. // In this case, we inspect the .config directories directly.
if (Directory.Exists(CONFIGURATION_PLUGINS_ROOT)) if (Directory.Exists(ENTERPRISE_CONFIGURATION_PLUGINS_ROOT))
{ {
foreach (var pluginDirectory in Directory.EnumerateDirectories(CONFIGURATION_PLUGINS_ROOT)) foreach (var pluginDirectory in Directory.EnumerateDirectories(ENTERPRISE_CONFIGURATION_PLUGINS_ROOT))
{ {
var directoryName = Path.GetFileName(pluginDirectory); var directoryName = Path.GetFileName(pluginDirectory);
if (!Guid.TryParse(directoryName, out var pluginId)) if (!Guid.TryParse(directoryName, out var pluginId))
@ -36,7 +36,7 @@ public static partial class PluginFactory
var deployFlag = ReadDeployFlagFromPluginFile(pluginDirectory); var deployFlag = ReadDeployFlagFromPluginFile(pluginDirectory);
var isManagedByConfigServer = deployFlag ?? true; var isManagedByConfigServer = deployFlag ?? true;
if (!deployFlag.HasValue) if (!deployFlag.HasValue)
LOG.LogWarning($"Configuration plugin '{pluginId}' does not define 'DEPLOYED_USING_CONFIG_SERVER'. Falling back to the plugin path and treating it as managed because it is stored under '{CONFIGURATION_PLUGINS_ROOT}'."); LOG.LogWarning($"Configuration plugin '{pluginId}' does not define 'DEPLOYED_USING_CONFIG_SERVER'. Falling back to the plugin path and treating it as managed because it is stored under '{ENTERPRISE_CONFIGURATION_PLUGINS_ROOT}'.");
if (isManagedByConfigServer) if (isManagedByConfigServer)
pluginIdsToRemove.Add(pluginId); pluginIdsToRemove.Add(pluginId);
@ -106,7 +106,7 @@ public static partial class PluginFactory
private static void DeleteConfigurationPluginDirectory(Guid pluginId) private static void DeleteConfigurationPluginDirectory(Guid pluginId)
{ {
var pluginDirectory = Path.Join(CONFIGURATION_PLUGINS_ROOT, pluginId.ToString()); var pluginDirectory = Path.Join(ENTERPRISE_CONFIGURATION_PLUGINS_ROOT, pluginId.ToString());
if (!Directory.Exists(pluginDirectory)) if (!Directory.Exists(pluginDirectory))
{ {
LOG.LogWarning($"Plugin directory '{pluginDirectory}' does not exist."); LOG.LogWarning($"Plugin directory '{pluginDirectory}' does not exist.");

View File

@ -11,7 +11,16 @@ public static partial class PluginFactory
private static string DATA_DIR = string.Empty; private static string DATA_DIR = string.Empty;
private static string PLUGINS_ROOT = string.Empty; private static string PLUGINS_ROOT = string.Empty;
private static string INTERNAL_PLUGINS_ROOT = string.Empty; private static string INTERNAL_PLUGINS_ROOT = string.Empty;
private static string CONFIGURATION_PLUGINS_ROOT = string.Empty;
/// <summary>
/// The directory the config server downloads the configuration plugins of an organization into.
/// </summary>
/// <remarks>
/// This is not the home of configuration plugins in general: a local configuration plugin can
/// live in any directory below the plugins root. Only the IT department of an organization
/// deploys plugins here, each in a directory named after its configuration ID.
/// </remarks>
private static string ENTERPRISE_CONFIGURATION_PLUGINS_ROOT = string.Empty;
private static string HOT_RELOAD_LOCK_FILE = string.Empty; private static string HOT_RELOAD_LOCK_FILE = string.Empty;
private static FileSystemWatcher HOT_RELOAD_WATCHER = null!; private static FileSystemWatcher HOT_RELOAD_WATCHER = null!;
@ -65,7 +74,7 @@ public static partial class PluginFactory
PLUGINS_ROOT = Path.Join(DATA_DIR, "plugins"); PLUGINS_ROOT = Path.Join(DATA_DIR, "plugins");
HOT_RELOAD_LOCK_FILE = Path.Join(PLUGINS_ROOT, ".lock"); HOT_RELOAD_LOCK_FILE = Path.Join(PLUGINS_ROOT, ".lock");
INTERNAL_PLUGINS_ROOT = Path.Join(PLUGINS_ROOT, ".internal"); INTERNAL_PLUGINS_ROOT = Path.Join(PLUGINS_ROOT, ".internal");
CONFIGURATION_PLUGINS_ROOT = Path.Join(PLUGINS_ROOT, ".config"); ENTERPRISE_CONFIGURATION_PLUGINS_ROOT = Path.Join(PLUGINS_ROOT, ".config");
if (!Directory.Exists(PLUGINS_ROOT)) if (!Directory.Exists(PLUGINS_ROOT))
Directory.CreateDirectory(PLUGINS_ROOT); Directory.CreateDirectory(PLUGINS_ROOT);
@ -89,12 +98,12 @@ public static partial class PluginFactory
/// <returns>True when the directory is nested in the enterprise configuration directory.</returns> /// <returns>True when the directory is nested in the enterprise configuration directory.</returns>
private static bool IsEnterpriseConfigurationPath(string? pluginPath) private static bool IsEnterpriseConfigurationPath(string? pluginPath)
{ {
if (string.IsNullOrWhiteSpace(pluginPath) || string.IsNullOrWhiteSpace(CONFIGURATION_PLUGINS_ROOT)) if (string.IsNullOrWhiteSpace(pluginPath) || string.IsNullOrWhiteSpace(ENTERPRISE_CONFIGURATION_PLUGINS_ROOT))
return false; return false;
try try
{ {
var configurationRoot = Path.GetFullPath(CONFIGURATION_PLUGINS_ROOT).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar; var configurationRoot = Path.GetFullPath(ENTERPRISE_CONFIGURATION_PLUGINS_ROOT).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar;
var pluginDirectory = Path.GetFullPath(pluginPath).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar; var pluginDirectory = Path.GetFullPath(pluginPath).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar;
return pluginDirectory.StartsWith(configurationRoot, StringComparison.OrdinalIgnoreCase); return pluginDirectory.StartsWith(configurationRoot, StringComparison.OrdinalIgnoreCase);
} }