Merge pull request #3557 from sbwalker/dev

a simple dependency manager for assemblies
This commit is contained in:
Shaun Walker 2023-12-15 11:06:01 -05:00 committed by GitHub
commit 6212d0a052
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 143 additions and 9 deletions

View File

@ -13,7 +13,6 @@ using System.Xml;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Oqtane.Controllers;
using Oqtane.Shared; using Oqtane.Shared;
// ReSharper disable AssignNullToNotNullAttribute // ReSharper disable AssignNullToNotNullAttribute
@ -41,6 +40,7 @@ namespace Oqtane.Infrastructure
} }
} }
// method must be static as it is called in ConfigureServices during Startup
public static string InstallPackages(string webRootPath, string contentRootPath) public static string InstallPackages(string webRootPath, string contentRootPath)
{ {
string errors = ""; string errors = "";
@ -52,8 +52,13 @@ namespace Oqtane.Infrastructure
Directory.CreateDirectory(sourceFolder); Directory.CreateDirectory(sourceFolder);
} }
// read assembly log
var assemblyLogPath = Path.Combine(sourceFolder, "assemblies.log");
var assemblies = GetAssemblyLog(assemblyLogPath);
// install Nuget packages in secure Packages folder // install Nuget packages in secure Packages folder
foreach (string packagename in Directory.GetFiles(sourceFolder, "*.nupkg")) var packages = Directory.GetFiles(sourceFolder, "*.nupkg");
foreach (string packagename in packages)
{ {
try try
{ {
@ -122,10 +127,29 @@ namespace Oqtane.Infrastructure
// ContentRootPath sometimes produces inconsistent path casing - so can't use string.Replace() // ContentRootPath sometimes produces inconsistent path casing - so can't use string.Replace()
filename = Regex.Replace(filename, Regex.Escape(contentRootPath), "", RegexOptions.IgnoreCase); filename = Regex.Replace(filename, Regex.Escape(contentRootPath), "", RegexOptions.IgnoreCase);
assets.Add(filename); assets.Add(filename);
if (!manifest && Path.GetExtension(filename) == ".log")
// packages can include a manifest (rather than relying on the framework to dynamically create one)
if (!manifest && filename.EndsWith(name + ".log"))
{ {
manifest = true; manifest = true;
} }
// register assembly
if (Path.GetExtension(filename) == ".dll")
{
// if package version was not installed previously
if (!File.Exists(Path.Combine(sourceFolder, name + ".log")))
{
if (assemblies.ContainsKey(Path.GetFileName(filename)))
{
assemblies[Path.GetFileName(filename)] += 1;
}
else
{
assemblies.Add(Path.GetFileName(filename), 1);
}
}
}
} }
} }
@ -155,6 +179,12 @@ namespace Oqtane.Infrastructure
File.Delete(packagename); File.Delete(packagename);
} }
if (packages.Length != 0)
{
// save assembly log
SetAssemblyLog(assemblyLogPath, assemblies);
}
return errors; return errors;
} }
@ -200,6 +230,10 @@ namespace Oqtane.Infrastructure
{ {
if (!string.IsNullOrEmpty(PackageName)) if (!string.IsNullOrEmpty(PackageName))
{ {
// read assembly log
var assemblyLogPath = Path.Combine(Path.Combine(_environment.ContentRootPath, Constants.PackagesFolder), "assemblies.log");
var assemblies = GetAssemblyLog(assemblyLogPath);
// get manifest with highest version // get manifest with highest version
string packagename = ""; string packagename = "";
string[] packages = Directory.GetFiles(Path.Combine(_environment.ContentRootPath, Constants.PackagesFolder), PackageName + "*.log"); string[] packages = Directory.GetFiles(Path.Combine(_environment.ContentRootPath, Constants.PackagesFolder), PackageName + "*.log");
@ -217,17 +251,31 @@ namespace Oqtane.Infrastructure
{ {
// legacy support for assets that were stored as absolute paths // legacy support for assets that were stored as absolute paths
string filepath = asset.StartsWith("\\") ? Path.Combine(_environment.ContentRootPath, asset.Substring(1)) : asset; string filepath = asset.StartsWith("\\") ? Path.Combine(_environment.ContentRootPath, asset.Substring(1)) : asset;
if (File.Exists(filepath))
// delete assets
if (Path.GetExtension(filepath) == ".dll")
{ {
// do not remove licensing assemblies - this is a temporary fix until a more robust dependency management solution is available // use assembly log to determine if assembly is used in other packages
if (!filepath.Contains("Oqtane.Licensing.")) if (assemblies.ContainsKey(Path.GetFileName(filepath)))
{ {
File.Delete(filepath); if (assemblies[Path.GetFileName(filepath)] == 1)
if (!Directory.EnumerateFiles(Path.GetDirectoryName(filepath)).Any())
{ {
Directory.Delete(Path.GetDirectoryName(filepath), true); DeleteFile(filepath);
assemblies.Remove(Path.GetFileName(filepath));
}
else
{
assemblies[Path.GetFileName(filepath)] -= 1;
} }
} }
else // does not exist in assembly log
{
DeleteFile(filepath);
}
}
else // not an assembly
{
DeleteFile(filepath);
} }
} }
@ -237,6 +285,9 @@ namespace Oqtane.Infrastructure
File.Delete(asset); File.Delete(asset);
} }
// save assembly log
SetAssemblyLog(assemblyLogPath, assemblies);
return true; return true;
} }
} }
@ -244,6 +295,76 @@ namespace Oqtane.Infrastructure
return false; return false;
} }
private void DeleteFile(string filepath)
{
if (File.Exists(filepath))
{
File.Delete(filepath);
if (!Directory.EnumerateFiles(Path.GetDirectoryName(filepath)).Any())
{
Directory.Delete(Path.GetDirectoryName(filepath), true);
}
}
}
public int RegisterAssemblies()
{
var assemblyLogPath = GetAssemblyLogPath();
var binFolder = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
var assemblies = GetAssemblyLog(assemblyLogPath);
// remove assemblies that no longer exist
foreach (var dll in assemblies)
{
if (!File.Exists(Path.Combine(binFolder, dll.Key)))
{
assemblies.Remove(dll.Key);
}
}
// add assemblies which are not registered
foreach (var dll in Directory.GetFiles(binFolder, "*.dll"))
{
if (!assemblies.ContainsKey(Path.GetFileName(dll)))
{
assemblies.Add(Path.GetFileName(dll), 1);
}
}
SetAssemblyLog(assemblyLogPath, assemblies);
return assemblies.Count;
}
private string GetAssemblyLogPath()
{
string packagesFolder = Path.Combine(_environment.ContentRootPath, Constants.PackagesFolder);
if (!Directory.Exists(packagesFolder))
{
Directory.CreateDirectory(packagesFolder);
}
return Path.Combine(packagesFolder, "assemblies.log");
}
private static Dictionary<string, int> GetAssemblyLog(string assemblyLogPath)
{
Dictionary<string, int> assemblies = new Dictionary<string, int>();
if (File.Exists(assemblyLogPath))
{
assemblies = JsonSerializer.Deserialize<Dictionary<string, int>>(File.ReadAllText(assemblyLogPath));
}
return assemblies;
}
private static void SetAssemblyLog(string assemblyLogPath, Dictionary<string, int> assemblies)
{
if (File.Exists(assemblyLogPath))
{
File.Delete(assemblyLogPath);
}
File.WriteAllText(assemblyLogPath, JsonSerializer.Serialize(assemblies, new JsonSerializerOptions { WriteIndented = true }));
}
public async Task UpgradeFramework() public async Task UpgradeFramework()
{ {
string folder = Path.Combine(_environment.ContentRootPath, Constants.PackagesFolder); string folder = Path.Combine(_environment.ContentRootPath, Constants.PackagesFolder);

View File

@ -6,6 +6,7 @@ namespace Oqtane.Infrastructure
{ {
void InstallPackages(); void InstallPackages();
bool UninstallPackage(string PackageName); bool UninstallPackage(string PackageName);
int RegisterAssemblies();
Task UpgradeFramework(); Task UpgradeFramework();
void RestartApplication(); void RestartApplication();
} }

View File

@ -32,6 +32,7 @@ namespace Oqtane.Infrastructure
var logRepository = provider.GetRequiredService<ILogRepository>(); var logRepository = provider.GetRequiredService<ILogRepository>();
var visitorRepository = provider.GetRequiredService<IVisitorRepository>(); var visitorRepository = provider.GetRequiredService<IVisitorRepository>();
var notificationRepository = provider.GetRequiredService<INotificationRepository>(); var notificationRepository = provider.GetRequiredService<INotificationRepository>();
var installationManager = provider.GetRequiredService<IInstallationManager>();
// iterate through sites for current tenant // iterate through sites for current tenant
List<Site> sites = siteRepository.GetSites().ToList(); List<Site> sites = siteRepository.GetSites().ToList();
@ -96,6 +97,17 @@ namespace Oqtane.Infrastructure
} }
} }
// register assemblies
try
{
var assemblies = installationManager.RegisterAssemblies();
log += assemblies.ToString() + " Assemblies Registered<br />";
}
catch (Exception ex)
{
log += $"Error Registering Assemblies - {ex.Message}<br />";
}
return log; return log;
} }