Run from docker
This commit is contained in:
25
Dockerfile
Normal file
25
Dockerfile
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
FROM mcr.microsoft.com/dotnet/sdk:7.0-alpine as BUILD
|
||||||
|
|
||||||
|
RUN apk add -U bash build-base clang icu-libs krb5-libs libgcc libintl libssl1.1 libstdc++ zlib-dev
|
||||||
|
WORKDIR /build
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN dotnet publish src/Alma/Alma.csproj -c Release -r linux-musl-x64 -p:PublishAot=true -o /app
|
||||||
|
|
||||||
|
FROM alpine:edge as FINAL
|
||||||
|
|
||||||
|
RUN mkdir /data
|
||||||
|
WORKDIR /data
|
||||||
|
|
||||||
|
ENV ALMA_APP_DATA_FALLBACK=/appdata
|
||||||
|
ENV ALMA_CONFIG_FALLBACK=/appconfig
|
||||||
|
|
||||||
|
RUN mkdir /appdata
|
||||||
|
RUN mkdir /appconfig
|
||||||
|
|
||||||
|
RUN apk add -U icu-libs libstdc++
|
||||||
|
|
||||||
|
COPY --from=BUILD /app/Alma /alma
|
||||||
|
|
||||||
|
ENTRYPOINT ["/alma"]
|
||||||
|
CMD ["help"]
|
||||||
116
src/Alma.App/Command/Diag/DiagCommand.cs
Normal file
116
src/Alma.App/Command/Diag/DiagCommand.cs
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using Alma.Logging;
|
||||||
|
|
||||||
|
namespace Alma.Command.Diag;
|
||||||
|
|
||||||
|
public class DiagCommand : ICommand
|
||||||
|
{
|
||||||
|
private readonly ILogger<DiagCommand> _logger;
|
||||||
|
public string CommandString => "diag";
|
||||||
|
private readonly Lazy<IReadOnlyList<MethodInfo>> _diagnosticHelpersLazy;
|
||||||
|
|
||||||
|
public DiagCommand(ILogger<DiagCommand> logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_diagnosticHelpersLazy = new Lazy<IReadOnlyList<MethodInfo>>(FindDiagnosticHelpers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task Run(List<string> parameters)
|
||||||
|
{
|
||||||
|
if (parameters.Count < 1)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("No diagnostic helper specified.");
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
var command = parameters[0];
|
||||||
|
|
||||||
|
if (command == "list")
|
||||||
|
{
|
||||||
|
ListDiagnosticHelpers();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
var diagnosticHelpers = _diagnosticHelpersLazy.Value;
|
||||||
|
|
||||||
|
var helper = diagnosticHelpers.FirstOrDefault(
|
||||||
|
h => GetDiagnosticHelper(h) is { } attr && attr.Command == command
|
||||||
|
);
|
||||||
|
|
||||||
|
if (helper is null)
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"Diagnostic helper {command} is not found.");
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!helper.IsStatic)
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"Diagnostic helper {helper.Name} is not static.");
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
HandleHelper(helper, parameters.Skip(1));
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ListDiagnosticHelpers()
|
||||||
|
{
|
||||||
|
var diagnosticHelpers = _diagnosticHelpersLazy.Value;
|
||||||
|
var commands = diagnosticHelpers
|
||||||
|
.Select(h => GetDiagnosticHelper(h)?.Command)
|
||||||
|
.OfType<string>()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
_logger.LogInformation("Available diagnostic helpers:");
|
||||||
|
foreach (var command in commands)
|
||||||
|
{
|
||||||
|
_logger.LogInformation(command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleHelper(MethodInfo helper, IEnumerable<string> parameters)
|
||||||
|
{
|
||||||
|
var helperParameters = helper.GetParameters();
|
||||||
|
var helperArguments = new object[helperParameters.Length];
|
||||||
|
for (var i = 0; i < helperParameters.Length; i++)
|
||||||
|
{
|
||||||
|
var parameterType = helperParameters[i].ParameterType;
|
||||||
|
if (parameterType == typeof(IEnumerable<string>))
|
||||||
|
{
|
||||||
|
helperArguments[i] = parameters;
|
||||||
|
}
|
||||||
|
else if (parameterType == typeof(ILogger))
|
||||||
|
{
|
||||||
|
helperArguments[i] = _logger;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"Diagnostic helper {helper.Name} has wrong parameter type, could not resolve '{parameterType}'.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
helper.Invoke(null, helperArguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IReadOnlyList<MethodInfo> FindDiagnosticHelpers()
|
||||||
|
{
|
||||||
|
var assemblies = new[]
|
||||||
|
{
|
||||||
|
Assembly.GetExecutingAssembly(),
|
||||||
|
};
|
||||||
|
|
||||||
|
return assemblies.SelectMany(
|
||||||
|
a =>
|
||||||
|
a
|
||||||
|
.GetTypes()
|
||||||
|
.SelectMany(t => t.GetMethods())
|
||||||
|
.Where(t => t.GetCustomAttributes(typeof(DiagnosticHelper)).Any()))
|
||||||
|
.ToList()
|
||||||
|
.AsReadOnly();
|
||||||
|
}
|
||||||
|
|
||||||
|
private DiagnosticHelper? GetDiagnosticHelper(MethodInfo o)
|
||||||
|
=> o.GetCustomAttributes(typeof(DiagnosticHelper)).FirstOrDefault() as DiagnosticHelper;
|
||||||
|
}
|
||||||
12
src/Alma.App/Command/Diag/DiagnosticHelper.cs
Normal file
12
src/Alma.App/Command/Diag/DiagnosticHelper.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace Alma.Command.Diag;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
|
public class DiagnosticHelper : Attribute
|
||||||
|
{
|
||||||
|
public string Command { get; }
|
||||||
|
|
||||||
|
public DiagnosticHelper(string command)
|
||||||
|
{
|
||||||
|
Command = command;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,47 +1,70 @@
|
|||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using Alma.Command.Diag;
|
||||||
|
using Alma.Logging;
|
||||||
|
|
||||||
namespace Alma.Services;
|
namespace Alma.Services;
|
||||||
|
|
||||||
public class FolderService : IFolderService
|
public class FolderService : IFolderService
|
||||||
{
|
{
|
||||||
|
private readonly Dictionary<string, Func<string?>> _configHomeProviders;
|
||||||
|
private readonly Dictionary<string, Func<string?>> _appDataProviders;
|
||||||
public string? ConfigRoot { get; }
|
public string? ConfigRoot { get; }
|
||||||
public string AppData { get; }
|
public string AppData { get; }
|
||||||
|
|
||||||
public string ApplicationSubfolderName => "alma";
|
public string ApplicationSubfolderName => "alma";
|
||||||
|
|
||||||
|
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(FolderService))]
|
||||||
public FolderService()
|
public FolderService()
|
||||||
{
|
{
|
||||||
|
_configHomeProviders = ConfigHomeProviders();
|
||||||
|
_appDataProviders = AppDataProviders();
|
||||||
|
|
||||||
ConfigRoot = GetConfigHomePath();
|
ConfigRoot = GetConfigHomePath();
|
||||||
AppData = GetAppDataPath();
|
AppData = GetAppDataPath();
|
||||||
|
|
||||||
if (!Directory.Exists(AppData)) Directory.CreateDirectory(AppData);
|
if (!Directory.Exists(AppData)) Directory.CreateDirectory(AppData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetPreferredConfigurationFolder()
|
private static Dictionary<string, Func<string?>> ConfigHomeProviders()
|
||||||
|
{
|
||||||
|
return new Dictionary<string, Func<string?>>
|
||||||
|
{
|
||||||
|
{"ALMA_CONFIG", () => Environment.GetEnvironmentVariable("ALMA_CONFIG")},
|
||||||
|
{"XDG_CONFIG_HOME", () => Environment.GetEnvironmentVariable("XDG_CONFIG_HOME")},
|
||||||
|
{"UserProfile", GetPreferredConfigurationFolder},
|
||||||
|
{"ALMA_CONFIG_FALLBACK", () => Environment.GetEnvironmentVariable("ALMA_CONFIG_FALLBACK")},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Dictionary<string, Func<string?>> AppDataProviders()
|
||||||
|
{
|
||||||
|
return new Dictionary<string, Func<string?>>
|
||||||
|
{
|
||||||
|
{"ALMA_APP_DATA", () => Environment.GetEnvironmentVariable("ALMA_APP_DATA")},
|
||||||
|
{"LocalApplicationData", () => Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)},
|
||||||
|
{"ALMA_APP_DATA_FALLBACK", () => Environment.GetEnvironmentVariable("ALMA_APP_DATA_FALLBACK")},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
string IFolderService.GetPreferredConfigurationFolder()
|
||||||
|
=> GetPreferredConfigurationFolder();
|
||||||
|
|
||||||
|
public static string GetPreferredConfigurationFolder()
|
||||||
=> Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".config");
|
=> Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".config");
|
||||||
|
|
||||||
private string? GetConfigHomePath()
|
private string? GetConfigHomePath()
|
||||||
{
|
{
|
||||||
var configHomeProviders = new List<Func<string?>>
|
var configHome = EnumerateProviders(_configHomeProviders.Values);
|
||||||
{
|
|
||||||
() => Environment.GetEnvironmentVariable("XDG_CONFIG_HOME"),
|
|
||||||
() => GetPreferredConfigurationFolder()
|
|
||||||
};
|
|
||||||
|
|
||||||
var configHome = EnumerateProviders(configHomeProviders);
|
|
||||||
return configHome == null ? null : Path.Combine(configHome, ApplicationSubfolderName);
|
return configHome == null ? null : Path.Combine(configHome, ApplicationSubfolderName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetAppDataPath()
|
private string GetAppDataPath()
|
||||||
{
|
{
|
||||||
var appDataProviders = new List<Func<string?>>
|
var appData = EnumerateProviders(_appDataProviders.Values) ?? Environment.CurrentDirectory;
|
||||||
{
|
|
||||||
() => Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)
|
|
||||||
};
|
|
||||||
|
|
||||||
var appData = EnumerateProviders(appDataProviders) ?? Environment.CurrentDirectory;
|
|
||||||
return Path.Combine(appData, ApplicationSubfolderName);
|
return Path.Combine(appData, ApplicationSubfolderName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string? EnumerateProviders(List<Func<string?>> providers)
|
private static string? EnumerateProviders(IEnumerable<Func<string?>> providers)
|
||||||
{
|
{
|
||||||
string? result = null;
|
string? result = null;
|
||||||
|
|
||||||
@@ -53,4 +76,26 @@ public class FolderService : IFolderService
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[DiagnosticHelper("config-home-providers")]
|
||||||
|
public static void ConfigHomeProviderDiag(ILogger logger)
|
||||||
|
{
|
||||||
|
var configHomeProviders = ConfigHomeProviders();
|
||||||
|
logger.LogInformation($"There are {configHomeProviders.Count} config home providers:");
|
||||||
|
foreach (var configHome in configHomeProviders)
|
||||||
|
{
|
||||||
|
logger.LogInformation($"{configHome.Key} => {configHome.Value() ?? "<null>"}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[DiagnosticHelper("app-data-providers")]
|
||||||
|
public static void AppDataProviderDiag(ILogger logger)
|
||||||
|
{
|
||||||
|
var appDataProviders = AppDataProviders();
|
||||||
|
logger.LogInformation($"There are {appDataProviders.Count} app data providers:");
|
||||||
|
foreach (var appData in appDataProviders)
|
||||||
|
{
|
||||||
|
logger.LogInformation($"{appData.Key} => {appData.Value() ?? "<null>"}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
using Alma.Data;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using Alma.Command.Diag;
|
||||||
|
using Alma.Data;
|
||||||
|
using Alma.Logging;
|
||||||
|
|
||||||
namespace Alma.Services;
|
namespace Alma.Services;
|
||||||
|
|
||||||
@@ -10,6 +13,7 @@ public class PathHelperService : IPathHelperService
|
|||||||
new("%DOCUMENTS%", () => Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)),
|
new("%DOCUMENTS%", () => Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(PathHelperService))]
|
||||||
public string ResolvePath(string path, string? currentDirectory = null)
|
public string ResolvePath(string path, string? currentDirectory = null)
|
||||||
{
|
{
|
||||||
var skipCombiningCurrentDirectory = false;
|
var skipCombiningCurrentDirectory = false;
|
||||||
@@ -30,4 +34,14 @@ public class PathHelperService : IPathHelperService
|
|||||||
? path
|
? path
|
||||||
: Path.Combine(currentDirectory, path);
|
: Path.Combine(currentDirectory, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[DiagnosticHelper("special-path-resolver")]
|
||||||
|
public static void SpecialPathResolverDiag(ILogger logger)
|
||||||
|
{
|
||||||
|
logger.LogInformation($"There are {_specialPathResolvers.Count} special path resolvers:");
|
||||||
|
foreach (var specialPathResolver in _specialPathResolvers)
|
||||||
|
{
|
||||||
|
logger.LogInformation($"{specialPathResolver.PathName} => {specialPathResolver.Resolver()}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace Alma.Logging;
|
namespace Alma.Logging;
|
||||||
|
|
||||||
public interface ILogger<T>
|
public interface ILogger
|
||||||
{
|
{
|
||||||
LogLevel DefaultLogLevel { get; }
|
LogLevel DefaultLogLevel { get; }
|
||||||
void LogInformation(string logMessage);
|
void LogInformation(string logMessage);
|
||||||
@@ -10,3 +10,8 @@ public interface ILogger<T>
|
|||||||
void LogError(string logMessage);
|
void LogError(string logMessage);
|
||||||
void LogCritical(string logMessage);
|
void LogCritical(string logMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface ILogger<T> : ILogger
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -3,5 +3,6 @@
|
|||||||
public interface ILoggerFactory
|
public interface ILoggerFactory
|
||||||
{
|
{
|
||||||
ILogger<T> CreateLogger<T>();
|
ILogger<T> CreateLogger<T>();
|
||||||
|
ILogger CreateLogger(Type t);
|
||||||
LogLevel DefaultLogLevel { get; }
|
LogLevel DefaultLogLevel { get; }
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
namespace Alma.Logging;
|
namespace Alma.Logging;
|
||||||
|
|
||||||
public class Logger<T> : ILogger<T>
|
|
||||||
|
public class Logger : ILogger
|
||||||
{
|
{
|
||||||
public LogLevel DefaultLogLevel { get; }
|
public LogLevel DefaultLogLevel { get; }
|
||||||
|
|
||||||
public Logger(LogLevel defaultLogLevel)
|
public Logger(LogLevel defaultLogLevel, string topicName)
|
||||||
{
|
{
|
||||||
DefaultLogLevel = defaultLogLevel;
|
DefaultLogLevel = defaultLogLevel;
|
||||||
}
|
}
|
||||||
@@ -33,3 +34,11 @@ public class Logger<T> : ILogger<T>
|
|||||||
Log(logMessage, LogLevel.Critical);
|
Log(logMessage, LogLevel.Critical);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class Logger<T> : Logger, ILogger<T>
|
||||||
|
{
|
||||||
|
public Logger(LogLevel defaultLogLevel) : base(defaultLogLevel, typeof(T).Name)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,4 +13,9 @@ public class LoggerFactory : ILoggerFactory
|
|||||||
{
|
{
|
||||||
return new Logger<T>(DefaultLogLevel);
|
return new Logger<T>(DefaultLogLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ILogger CreateLogger(Type t)
|
||||||
|
{
|
||||||
|
return new Logger(DefaultLogLevel, t.Name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
using Alma.Command;
|
using System.IO;
|
||||||
|
using System;
|
||||||
|
using Alma.Command;
|
||||||
using Alma.Command.Configure;
|
using Alma.Command.Configure;
|
||||||
|
using Alma.Command.Diag;
|
||||||
using Alma.Command.Help;
|
using Alma.Command.Help;
|
||||||
using Alma.Command.Info;
|
using Alma.Command.Info;
|
||||||
using Alma.Command.Install;
|
using Alma.Command.Install;
|
||||||
@@ -19,6 +22,14 @@ public static class Program
|
|||||||
{
|
{
|
||||||
InitLogging();
|
InitLogging();
|
||||||
|
|
||||||
|
var logger = AlmaLoggerFactory.CreateLogger(typeof(Program));
|
||||||
|
|
||||||
|
var workdir = GetWorkdir(logger);
|
||||||
|
if (workdir != null)
|
||||||
|
{
|
||||||
|
Environment.CurrentDirectory = workdir;
|
||||||
|
}
|
||||||
|
|
||||||
var services = new AlmaServiceProvider();
|
var services = new AlmaServiceProvider();
|
||||||
|
|
||||||
var repositoryConfiguration = services.GetService<IRepositoryConfiguration>();
|
var repositoryConfiguration = services.GetService<IRepositoryConfiguration>();
|
||||||
@@ -28,6 +39,33 @@ public static class Program
|
|||||||
await application.Run(args);
|
await application.Run(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string? GetWorkdir(ILogger logger)
|
||||||
|
{
|
||||||
|
var workdirProviders = new Dictionary<string, Func<string?>>
|
||||||
|
{
|
||||||
|
{"ALMA_WORKDIR", () => Environment.GetEnvironmentVariable("ALMA_WORKDIR")},
|
||||||
|
{"WORKDIR", () => Environment.GetEnvironmentVariable("WORKDIR")},
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var workdirProvider in workdirProviders)
|
||||||
|
{
|
||||||
|
var workdir = workdirProvider.Value();
|
||||||
|
if (workdir != null)
|
||||||
|
{
|
||||||
|
if (Directory.Exists(workdir))
|
||||||
|
{
|
||||||
|
return workdir;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.LogInformation($"{workdirProvider.Key} is set to {workdir} but this directory does not exist.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private static void InitLogging()
|
private static void InitLogging()
|
||||||
{
|
{
|
||||||
AlmaLoggerFactory = new LoggerFactory();
|
AlmaLoggerFactory = new LoggerFactory();
|
||||||
@@ -49,6 +87,7 @@ public static class Program
|
|||||||
[Singleton(typeof(ICommand), typeof(InstallCommand))]
|
[Singleton(typeof(ICommand), typeof(InstallCommand))]
|
||||||
[Singleton(typeof(ICommand), typeof(HelpCommand))]
|
[Singleton(typeof(ICommand), typeof(HelpCommand))]
|
||||||
[Singleton(typeof(ICommand), typeof(ConfigureCommand))]
|
[Singleton(typeof(ICommand), typeof(ConfigureCommand))]
|
||||||
|
[Singleton(typeof(ICommand), typeof(DiagCommand))]
|
||||||
[Singleton(typeof(IModuleConfigurationResolver), typeof(ModuleConfigurationResolver))]
|
[Singleton(typeof(IModuleConfigurationResolver), typeof(ModuleConfigurationResolver))]
|
||||||
[Singleton(typeof(IMetadataHandler), typeof(MetadataHandler))]
|
[Singleton(typeof(IMetadataHandler), typeof(MetadataHandler))]
|
||||||
[Singleton(typeof(IShellService), typeof(ShellService))]
|
[Singleton(typeof(IShellService), typeof(ShellService))]
|
||||||
|
|||||||
Reference in New Issue
Block a user