From 30c3266e255bd59ee552015d837ed09ba4fa44ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81d=C3=A1m=20Kov=C3=A1cs?= Date: Sun, 6 Nov 2022 18:36:04 +0100 Subject: [PATCH] Install commands, improvements --- .../Module/ModuleConfiguration.cs | 10 ++- .../Services/IOsInformation.cs | 4 +- .../Services/IShellService.cs | 6 ++ src/Alma.App/Alma.App.csproj | 4 + .../{ModuleInfoCommand.cs => InfoCommand.cs} | 25 ++++--- .../Command/Install/InstallCommand.cs | 74 +++++++++++++++++++ src/Alma.App/Command/Link/LinkCommand.cs | 27 ++----- .../Command/RepositoryModuleCommandBase.cs | 46 ++++++++++++ .../Services/ModuleConfigurationResolver.cs | 5 +- src/Alma.App/Services/OsInformation.cs | 49 ++++++++++-- src/Alma.App/Services/ShellService.cs | 52 +++++++++++++ src/Alma.Logging/ILogger.cs | 2 + src/Alma.Logging/LogLevel.cs | 2 + src/Alma.Logging/Logger.cs | 10 +++ src/Alma/Program.cs | 49 ++---------- 15 files changed, 278 insertions(+), 87 deletions(-) create mode 100644 src/Alma.Abstraction/Services/IShellService.cs rename src/Alma.App/Command/Info/{ModuleInfoCommand.cs => InfoCommand.cs} (70%) create mode 100644 src/Alma.App/Command/Install/InstallCommand.cs create mode 100644 src/Alma.App/Command/RepositoryModuleCommandBase.cs create mode 100644 src/Alma.App/Services/ShellService.cs diff --git a/src/Alma.Abstraction/Configuration/Module/ModuleConfiguration.cs b/src/Alma.Abstraction/Configuration/Module/ModuleConfiguration.cs index 9c9c963..22c9091 100644 --- a/src/Alma.Abstraction/Configuration/Module/ModuleConfiguration.cs +++ b/src/Alma.Abstraction/Configuration/Module/ModuleConfiguration.cs @@ -5,10 +5,13 @@ public class ModuleConfiguration public string? Target { get; set; } public Dictionary? Links { get; set; } - public ModuleConfiguration(string? target, Dictionary? links) + public string? Install { get; set; } + + public ModuleConfiguration(string? target, Dictionary? links, string? install) { Target = target; Links = links; + Install = install; } public ModuleConfiguration Merge(ModuleConfiguration merge) @@ -17,10 +20,11 @@ public class ModuleConfiguration .Concat(merge.Links ?? new Dictionary()); return new ModuleConfiguration( merge.Target ?? Target, - new Dictionary(mergedLinks) + new Dictionary(mergedLinks), + merge.Install ?? Install ); } public static ModuleConfiguration Empty() => - new(null, new Dictionary()); + new(null, new Dictionary(), null); } \ No newline at end of file diff --git a/src/Alma.Abstraction/Services/IOsInformation.cs b/src/Alma.Abstraction/Services/IOsInformation.cs index 6dd7429..5051eea 100644 --- a/src/Alma.Abstraction/Services/IOsInformation.cs +++ b/src/Alma.Abstraction/Services/IOsInformation.cs @@ -2,6 +2,6 @@ namespace Alma.Services; public interface IOsInformation { - string GetOsIdentifier(); - bool IsOnPlatform(string platform); + Task GetOsIdentifierAsync(); + Task IsOnPlatformAsync(string platform); } \ No newline at end of file diff --git a/src/Alma.Abstraction/Services/IShellService.cs b/src/Alma.Abstraction/Services/IShellService.cs new file mode 100644 index 0000000..0465570 --- /dev/null +++ b/src/Alma.Abstraction/Services/IShellService.cs @@ -0,0 +1,6 @@ +namespace Alma.Services; + +public interface IShellService +{ + Task RunCommandAsync(string command); +} \ No newline at end of file diff --git a/src/Alma.App/Alma.App.csproj b/src/Alma.App/Alma.App.csproj index 365f292..e604b84 100644 --- a/src/Alma.App/Alma.App.csproj +++ b/src/Alma.App/Alma.App.csproj @@ -5,6 +5,10 @@ + + + + net6.0 enable diff --git a/src/Alma.App/Command/Info/ModuleInfoCommand.cs b/src/Alma.App/Command/Info/InfoCommand.cs similarity index 70% rename from src/Alma.App/Command/Info/ModuleInfoCommand.cs rename to src/Alma.App/Command/Info/InfoCommand.cs index d07e0cc..8a59a92 100644 --- a/src/Alma.App/Command/Info/ModuleInfoCommand.cs +++ b/src/Alma.App/Command/Info/InfoCommand.cs @@ -4,47 +4,52 @@ using Alma.Services; namespace Alma.Command.Info; -public class ModuleInfoCommand : ICommand +public class InfoCommand : ICommand { public string CommandString => "info"; private readonly IFolderService _folderService; private readonly IRepositoryConfiguration _repositoryConfiguration; - private readonly ILogger _logger; + private readonly ILogger _logger; + private readonly IOsInformation _osInformation; - public ModuleInfoCommand( + public InfoCommand( IFolderService folderService, IRepositoryConfiguration repositoryConfiguration, - ILogger logger + ILogger logger, + IOsInformation osInformation ) { _folderService = folderService; _repositoryConfiguration = repositoryConfiguration; _logger = logger; + _osInformation = osInformation; } - public Task Run(List parameters) + public async Task Run(List parameters) { //Add info REPO //Add info REPO MODULE _logger.LogInformation("AppData folder: " + _folderService.AppData); - if (_folderService.ConfigRoot is string configRoot) + if (_folderService.ConfigRoot is { } configRoot) { _logger.LogInformation("Configuration folder: " + configRoot); } else { _logger.LogInformation("Configuration folder not exists."); - _logger.LogInformation("Preffered configuration folder is: " + Path.Combine(_folderService.GetPreferredConfigurationFolder(), _folderService.ApplicationSubfolderName)); + _logger.LogInformation("Preferred configuration folder is: " + Path.Combine(_folderService.GetPreferredConfigurationFolder(), _folderService.ApplicationSubfolderName)); } + _logger.LogInformation(""); + _logger.LogInformation($"Platform is '{await _osInformation.GetOsIdentifierAsync()}'"); _logger.LogInformation(""); - if (_repositoryConfiguration.Configuration.Repositories is var repositores && repositores?.Count > 0) + if (_repositoryConfiguration.Configuration.Repositories is {Count: > 0} repositories) { _logger.LogInformation("Repositories:"); - foreach (var repository in repositores) + foreach (var repository in repositories) { Console.Write(repository.Name); if (repository.RepositoryPath is not null && !Directory.Exists(repository.RepositoryPath)) @@ -58,7 +63,5 @@ public class ModuleInfoCommand : ICommand { _logger.LogInformation("No repositories found"); } - - return Task.CompletedTask; } } \ No newline at end of file diff --git a/src/Alma.App/Command/Install/InstallCommand.cs b/src/Alma.App/Command/Install/InstallCommand.cs new file mode 100644 index 0000000..41cec98 --- /dev/null +++ b/src/Alma.App/Command/Install/InstallCommand.cs @@ -0,0 +1,74 @@ +using System.Diagnostics; +using Alma.Configuration.Repository; +using Alma.Data; +using Alma.Logging; +using Alma.Services; + +namespace Alma.Command.Install; + +public class InstallCommand : RepositoryModuleCommandBase +{ + private readonly ILogger _logger; + private readonly IModuleConfigurationResolver _moduleConfigurationResolver; + private readonly IShellService _shellService; + public override string CommandString => "install"; + + public InstallCommand( + ILogger logger, + IRepositoryConfiguration repositoryConfiguration, + IModuleConfigurationResolver moduleConfigurationResolver, + IShellService shellService) + : base(repositoryConfiguration) + { + _logger = logger; + _moduleConfigurationResolver = moduleConfigurationResolver; + _shellService = shellService; + } + + public override async Task Run(List parameters) + { + var (repoName, moduleName) = GetRepositoryAndModuleName(parameters); + if (moduleName is null) + { + _logger.LogInformation("No module specified"); + return; + } + + string sourceDirectory = Path.Combine(Environment.CurrentDirectory); + string targetDirectory = Path.Combine(Environment.CurrentDirectory, ".."); + + string moduleNameAsPath = moduleName.Replace('/', Path.DirectorySeparatorChar); + (sourceDirectory, _) = GetModuleSourceAndTargetDirectory(repoName, sourceDirectory, targetDirectory); + + string moduleDirectory = Path.Combine(sourceDirectory, moduleNameAsPath); + + var moduleConfigFileStub = Path.Combine(moduleDirectory, Constants.ModuleConfigFileStub); + var (moduleConfiguration, moduleConfigurationFile) = await _moduleConfigurationResolver.ResolveModuleConfiguration(moduleConfigFileStub); + + if (moduleConfiguration is null) + { + _logger.LogInformation("No module configuration found."); + return; + } + + var installLines = moduleConfiguration.Install?.Split(Environment.NewLine); + + if (installLines is null) + { + _logger.LogInformation("No install command is found"); + return; + } + + _logger.LogInformation($"Install command: {string.Join("\n", installLines)}"); + + if (installLines.Length == 1) + { + _logger.LogInformation("Running install command '" + installLines[0] + "'"); + await _shellService.RunCommandAsync(installLines[0]); + } + else + { + _logger.LogError("Multi line scripts are not currently supported"); + } + } +} \ No newline at end of file diff --git a/src/Alma.App/Command/Link/LinkCommand.cs b/src/Alma.App/Command/Link/LinkCommand.cs index 2864f44..6c4ce46 100644 --- a/src/Alma.App/Command/Link/LinkCommand.cs +++ b/src/Alma.App/Command/Link/LinkCommand.cs @@ -7,20 +7,21 @@ using Alma.Services; namespace Alma.Command.Link; -public class LinkCommand : ICommand +public class LinkCommand : RepositoryModuleCommandBase { private readonly IRepositoryConfiguration _repositoryConfiguration; private readonly IModuleConfigurationResolver _moduleConfigurationResolver; private readonly IMetadataHandler _metadataHandler; private readonly ILogger _logger; - public string CommandString => "link"; + public override string CommandString => "link"; public LinkCommand( IRepositoryConfiguration repositoryConfiguration, IModuleConfigurationResolver moduleConfigurationResolver, IMetadataHandler metadataHandler, ILogger logger) + : base(repositoryConfiguration) { _repositoryConfiguration = repositoryConfiguration; _moduleConfigurationResolver = moduleConfigurationResolver; @@ -28,26 +29,19 @@ public class LinkCommand : ICommand _logger = logger; } - public async Task Run(List parameters) + public override async Task Run(List parameters) { - if (parameters.Count == 0) + var (repoName, moduleName) = GetRepositoryAndModuleName(parameters); + if (moduleName is null) { _logger.LogInformation("No module specified"); return; } - string moduleName = parameters[0]; - string sourceDirectory = Path.Combine(Environment.CurrentDirectory); string targetDirectory = Path.Combine(Environment.CurrentDirectory, ".."); - var repoName = GetRepositoryName(parameters); - if (repoName is not null - && _repositoryConfiguration.Configuration.Repositories.FirstOrDefault(r => r.Name == repoName) is { } repoConfig) - { - sourceDirectory = repoConfig.RepositoryPath ?? sourceDirectory; - targetDirectory = repoConfig.LinkPath ?? targetDirectory; - } + (sourceDirectory, targetDirectory) = GetModuleSourceAndTargetDirectory(repoName, sourceDirectory, targetDirectory); if (!Directory.Exists(sourceDirectory)) { @@ -180,13 +174,6 @@ public class LinkCommand : ICommand return filesToLink.Concat(subDirLinksToAdd); } - private static string? GetRepositoryName(List parameters) - { - //TODO: handle parameters - if (parameters.Count < 2) return null; - return parameters[1]; - } - private static string ResolvePath(string path, string currentDirectory) { if (path.StartsWith("~")) diff --git a/src/Alma.App/Command/RepositoryModuleCommandBase.cs b/src/Alma.App/Command/RepositoryModuleCommandBase.cs new file mode 100644 index 0000000..714acd6 --- /dev/null +++ b/src/Alma.App/Command/RepositoryModuleCommandBase.cs @@ -0,0 +1,46 @@ +using Alma.Configuration.Repository; + +namespace Alma.Command; + +public abstract class RepositoryModuleCommandBase : ICommand +{ + private readonly IRepositoryConfiguration _repositoryConfiguration; + public abstract string CommandString { get; } + public abstract Task Run(List parameters); + + protected RepositoryModuleCommandBase(IRepositoryConfiguration repositoryConfiguration) + { + _repositoryConfiguration = repositoryConfiguration; + } + + protected (string?, string?) GetRepositoryAndModuleName(List parameters) + { + //TODO: handle parameters + string? repositoryName = null; + string? moduleName = null; + + if (parameters.Count == 1) + { + moduleName = parameters[0]; + } + else if (parameters.Count >= 1) + { + repositoryName = parameters[0]; + moduleName = parameters[1]; + } + + return (repositoryName, moduleName); + } + + protected (string sourceDirectory, string targetDirectory) GetModuleSourceAndTargetDirectory(string? repoName, string fallbackSourceDirectory, string fallbackTargetDirectory) + { + if (repoName is not null + && _repositoryConfiguration.Configuration.Repositories.FirstOrDefault(r => r.Name == repoName) is { } repoConfig) + { + fallbackSourceDirectory = repoConfig.RepositoryPath ?? fallbackSourceDirectory; + fallbackTargetDirectory = repoConfig.LinkPath ?? fallbackTargetDirectory; + } + + return (fallbackSourceDirectory, fallbackTargetDirectory); + } +} \ No newline at end of file diff --git a/src/Alma.App/Services/ModuleConfigurationResolver.cs b/src/Alma.App/Services/ModuleConfigurationResolver.cs index a28d3b0..a08b561 100644 --- a/src/Alma.App/Services/ModuleConfigurationResolver.cs +++ b/src/Alma.App/Services/ModuleConfigurationResolver.cs @@ -21,7 +21,10 @@ public class ModuleConfigurationResolver : IModuleConfigurationResolver if (moduleConfigRoot is null) return (null, null); - var validModuleConfigurations = moduleConfigRoot.Where(m => _osInformation.IsOnPlatform(m.Key)); + var validModuleConfigurations = await moduleConfigRoot + .ToAsyncEnumerable() + .WhereAwait(async m => await _osInformation.IsOnPlatformAsync(m.Key)) + .ToListAsync(); //TODO: priority order var orderedValidModuleConfigurations = new Dictionary(validModuleConfigurations); diff --git a/src/Alma.App/Services/OsInformation.cs b/src/Alma.App/Services/OsInformation.cs index 931c1a6..c6048bd 100644 --- a/src/Alma.App/Services/OsInformation.cs +++ b/src/Alma.App/Services/OsInformation.cs @@ -6,19 +6,52 @@ public class OsInformation : IOsInformation { private const string OsIdentifierDefault = "default"; private const string OsIdentifierWin = "windows"; + private const string OsIdentifierMac = "macos"; + private const string OsIdentifierFreeBsd = "freebsd"; private const string OsIdentifierLinux = "linux"; - public string GetOsIdentifier() - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return OsIdentifierWin; - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) return OsIdentifierLinux; + private const string LinuxOsRelease = "/etc/os-release"; - return "unknown"; + public async Task GetOsIdentifierAsync() + { + string? baseOsIdentifier = null; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) baseOsIdentifier = OsIdentifierWin; + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) baseOsIdentifier = OsIdentifierMac; + else if (RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD)) baseOsIdentifier = OsIdentifierFreeBsd; + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + baseOsIdentifier = OsIdentifierLinux; + + try + { + if (File.Exists(LinuxOsRelease)) + { + var lines = await File.ReadAllLinesAsync(LinuxOsRelease); + var distroName = lines.FirstOrDefault(l => l.StartsWith("id=", StringComparison.InvariantCultureIgnoreCase)); + if (distroName is not null) + { + distroName = distroName.ToLower().Substring(distroName.IndexOf("=", StringComparison.Ordinal) + 1); + + baseOsIdentifier += "-" + distroName; + } + } + } + catch + { + } + } + + if (baseOsIdentifier is null) + return "unknown"; + + var architecture = RuntimeInformation.ProcessArchitecture.ToString().ToLower(); + + return baseOsIdentifier + "-" + architecture; } - public bool IsOnPlatform(string platform) + public async Task IsOnPlatformAsync(string platform) { - if (platform == OsIdentifierDefault) return true; - return platform == GetOsIdentifier(); + return platform == OsIdentifierDefault + || (await GetOsIdentifierAsync()).StartsWith(platform, StringComparison.InvariantCultureIgnoreCase); } } \ No newline at end of file diff --git a/src/Alma.App/Services/ShellService.cs b/src/Alma.App/Services/ShellService.cs new file mode 100644 index 0000000..5165ecd --- /dev/null +++ b/src/Alma.App/Services/ShellService.cs @@ -0,0 +1,52 @@ +using System.Diagnostics; +using System.Runtime.InteropServices; +using Alma.Logging; + +namespace Alma.Services; + +public class ShellService : IShellService +{ + private readonly ILogger _logger; + + public ShellService(ILogger logger) + { + _logger = logger; + } + + public async Task RunCommandAsync(string command) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + var processStartInfo = new ProcessStartInfo() + { + FileName = "sh", + ArgumentList = {"-c", command}, + RedirectStandardOutput = true, + RedirectStandardInput = true, + CreateNoWindow = true, + UseShellExecute = false + }; + + var process = Process.Start(processStartInfo); + if (process is null) return; + + var reader = process.StandardOutput; + while (!reader.EndOfStream) + { + var content = await reader.ReadLineAsync(); + + if (content is not null) + { + _logger.LogInformation(content); + } + } + + await process.WaitForExitAsync(); + return; + } + + _logger.LogError("Platform not supported"); + + throw new NotSupportedException(); + } +} \ No newline at end of file diff --git a/src/Alma.Logging/ILogger.cs b/src/Alma.Logging/ILogger.cs index 2bb57cd..33677e8 100644 --- a/src/Alma.Logging/ILogger.cs +++ b/src/Alma.Logging/ILogger.cs @@ -7,4 +7,6 @@ public interface ILogger void LogDebug(string logMessage); void LogTrace(string logMessage); void Log(string logMessage, LogLevel logLevel); + void LogError(string logMessage); + void LogCritical(string logMessage); } \ No newline at end of file diff --git a/src/Alma.Logging/LogLevel.cs b/src/Alma.Logging/LogLevel.cs index fd2ec99..e1c0b3d 100644 --- a/src/Alma.Logging/LogLevel.cs +++ b/src/Alma.Logging/LogLevel.cs @@ -2,6 +2,8 @@ public enum LogLevel { + Critical, + Error, Information, Debug, Trace diff --git a/src/Alma.Logging/Logger.cs b/src/Alma.Logging/Logger.cs index d6c324c..cb71945 100644 --- a/src/Alma.Logging/Logger.cs +++ b/src/Alma.Logging/Logger.cs @@ -22,4 +22,14 @@ public class Logger : ILogger Console.WriteLine(s); } } + + public void LogError(string logMessage) + { + Log(logMessage, LogLevel.Error); + } + + public void LogCritical(string logMessage) + { + Log(logMessage, LogLevel.Critical); + } } \ No newline at end of file diff --git a/src/Alma/Program.cs b/src/Alma/Program.cs index af22e2d..ab984f4 100644 --- a/src/Alma/Program.cs +++ b/src/Alma/Program.cs @@ -1,5 +1,6 @@ using Alma.Command; using Alma.Command.Info; +using Alma.Command.Install; using Alma.Command.Link; using Alma.Command.List; using Alma.Command.Unlink; @@ -12,42 +13,6 @@ namespace Alma; public static class Program { - /*public static async Task Main(string[] args) - { - var services = BuildServices(); - - var repositoryConfiguration = services.GetRequiredService(); - await repositoryConfiguration.LoadAsync(); - var application = services.GetRequiredService(); - - await application.Run(args); - - static IServiceProvider BuildServices() - { - var serviceCollection = new ServiceCollection(); - - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - - typeof(IRepositoryConfiguration), typeof(RepositoryConfiguration) - typeof(IFolderService), typeof(FolderService) - typeof(ConfigurationFileReader) - typeof(IConfigurationFileReader), typeof(JsonConfigurationFileReader) - typeof(IOsInformation), typeof(OsInformation) - typeof(ICommand), typeof(LinkCommand) - typeof(IModuleConfigurationResolver), typeof(ModuleConfigurationResolver) - typeof(Application) - - return serviceCollection.BuildServiceProvider(); - } - }*/ - public static async Task Main(string[] args) { InitLogging(); @@ -61,14 +26,12 @@ public static class Program await application.Run(args); } - private static ILoggerFactory InitLogging() + private static void InitLogging() { - var loggerFactory = new LoggerFactory(); - - return AlmaLoggerFactory = loggerFactory; + AlmaLoggerFactory = new LoggerFactory(); } - public static ILoggerFactory AlmaLoggerFactory { get; private set; } + public static ILoggerFactory AlmaLoggerFactory { get; private set; } = null!; } [ServiceProvider] @@ -79,12 +42,14 @@ public static class Program [Singleton(typeof(IOsInformation), typeof(OsInformation))] [Singleton(typeof(ICommand), typeof(LinkCommand))] [Singleton(typeof(ICommand), typeof(UnlinkCommand))] -[Singleton(typeof(ICommand), typeof(ModuleInfoCommand))] +[Singleton(typeof(ICommand), typeof(InfoCommand))] [Singleton(typeof(ICommand), typeof(ListCommand))] +[Singleton(typeof(ICommand), typeof(InstallCommand))] //Dependency cycle //[Singleton(typeof(ICommand), typeof(HelpCommand))] [Singleton(typeof(IModuleConfigurationResolver), typeof(ModuleConfigurationResolver))] [Singleton(typeof(IMetadataHandler), typeof(MetadataHandler))] +[Singleton(typeof(IShellService), typeof(ShellService))] [Singleton(typeof(Application))] [Transient(typeof(ILogger<>), Factory = nameof(CustomLoggerFactory))] internal partial class AlmaServiceProvider