diff --git a/Directory.Packages.props b/Directory.Packages.props index 90e587f1..663c0087 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -19,6 +19,7 @@ + diff --git a/src/Aspirate.Cli/Properties/launchSettings.json b/src/Aspirate.Cli/Properties/launchSettings.json index 277f475a..f4d60186 100644 --- a/src/Aspirate.Cli/Properties/launchSettings.json +++ b/src/Aspirate.Cli/Properties/launchSettings.json @@ -3,7 +3,7 @@ "init": { "commandName": "Project", "commandLineArgs": "init", - "workingDirectory": "/Users/prom3theu5/git/test-compose/AspireSample/AspireSample.AppHost", + "workingDirectory": "C:\\Users\\MKvamm\\source\\repos\\RIS\\src\\Ris.AppHost", "hotReloadEnabled": false }, "init-non-interactive": { @@ -14,8 +14,8 @@ }, "generate": { "commandName": "Project", - "commandLineArgs": "generate --skip-build --output-format compose", - "workingDirectory": "/Users/prom3theu5/git/tests/new-test/AspireSample/AspireSample.AppHost", + "commandLineArgs": "generate --skip-build", + "workingDirectory": "C:\\Users\\MKvamm\\source\\repos\\RIS\\src\\Ris.AppHost", "hotReloadEnabled": false }, "generate-non-interactive": { @@ -51,7 +51,7 @@ "apply": { "commandName": "Project", "commandLineArgs": "apply", - "workingDirectory": "/Users/prom3theu5/git/tests/new-test/AspireSample/AspireSample.AppHost", + "workingDirectory": "C:\\Users\\MKvamm\\source\\repos\\RIS\\src\\Ris.AppHost", "hotReloadEnabled": false }, "apply-non-interactive": { @@ -63,7 +63,7 @@ "destroy": { "commandName": "Project", "commandLineArgs": "destroy", - "workingDirectory": "/Users/prom3theu5/git/test-compose/AspireSample/AspireSample.AppHost", + "workingDirectory": "C:\\Users\\MKvamm\\source\\repos\\RIS\\src\\Ris.AppHost", "hotReloadEnabled": false }, "destroy-non-interactive": { @@ -73,4 +73,4 @@ "hotReloadEnabled": false } } -} +} \ No newline at end of file diff --git a/src/Aspirate.Cli/Templates/deployment.hbs b/src/Aspirate.Cli/Templates/deployment.hbs index c1bb7710..57d821f9 100644 --- a/src/Aspirate.Cli/Templates/deployment.hbs +++ b/src/Aspirate.Cli/Templates/deployment.hbs @@ -78,7 +78,7 @@ spec: {{#each bindMounts}} - name: bindmount-{{@index}} hostPath: - path: {{source}} + path: /mnt{{target}} type: DirectoryOrCreate {{/each}} {{/if}} \ No newline at end of file diff --git a/src/Aspirate.Commands/Actions/BindMounts/ApplyMinikubeMountsAction.cs b/src/Aspirate.Commands/Actions/BindMounts/ApplyMinikubeMountsAction.cs new file mode 100644 index 00000000..72b42b92 --- /dev/null +++ b/src/Aspirate.Commands/Actions/BindMounts/ApplyMinikubeMountsAction.cs @@ -0,0 +1,39 @@ +using System.Reflection.Metadata.Ecma335; +using Aspirate.Shared.Models.AspireManifests.Components.Common; +using Aspirate.Shared.Models.AspireManifests.Components.Common.Container; +using Microsoft.Extensions.DependencyInjection; + +namespace Aspirate.Commands.Actions.BindMounts; +public sealed class ApplyMinikubeMountsAction( + IServiceProvider serviceProvider, + IMinikubeCliService minikubeCliService) : BaseAction(serviceProvider) +{ + public override async Task ExecuteAsync() + { + Logger.WriteRuler("[purple]Handling minikube mounts[/]"); + await Task.Run(HandleMinikubeMounts); + + return true; + } + private void HandleMinikubeMounts() + { + if (CurrentState.KubeContext != "minikube" || CurrentState.DisableMinikubeMountAction.Equals(true)) + { + return; + } + + Logger.MarkupLine("Applying volume mounts to minikube..."); + + var minikubeCliInstalled = minikubeCliService.IsMinikubeCliInstalledOnMachine(); + + if (!minikubeCliInstalled) + { + Logger.MarkupLine("[yellow]Minikube cli is required to perform Minikube volume mounts.[/]"); + Logger.MarkupLine("[yellow]Please install minikube cli following the guide here:[blue]https://minikube.sigs.k8s.io/docs/start/?arch=%2Fwindows%2Fx86-64%2Fstable%2F.exe+download/[/][/]"); + Logger.MarkupLine("[yellow]Manifest deployment will continue, but Minikube volume mounts will not be applied by aspirate.[/]"); + return; + } + + minikubeCliService.ActivateMinikubeMount(CurrentState); + } +} diff --git a/src/Aspirate.Commands/Actions/BindMounts/KillMinikubeMountsAction.cs b/src/Aspirate.Commands/Actions/BindMounts/KillMinikubeMountsAction.cs new file mode 100644 index 00000000..24eb38a1 --- /dev/null +++ b/src/Aspirate.Commands/Actions/BindMounts/KillMinikubeMountsAction.cs @@ -0,0 +1,27 @@ +using System.Reflection.Metadata.Ecma335; +using Aspirate.Shared.Models.AspireManifests.Components.Common; +using Aspirate.Shared.Models.AspireManifests.Components.Common.Container; +using Microsoft.Extensions.DependencyInjection; + +namespace Aspirate.Commands.Actions.BindMounts; +public sealed class KillMinikubeMountsAction( + IServiceProvider serviceProvider, + IMinikubeCliService minikubeCliService) : BaseAction(serviceProvider) +{ + public override async Task ExecuteAsync() + { + Logger.WriteRuler("[purple]Handling minikube mounts[/]"); + + await Task.Run(HandleMinikubeMounts); + + return true; + } + private void HandleMinikubeMounts() + { + if (minikubeCliService.IsMinikubeCliInstalledOnMachine() && (CurrentState.DisableMinikubeMountAction.Equals(false) || !CurrentState.DisableMinikubeMountAction.HasValue)) + { + minikubeCliService.KillMinikubeMounts(CurrentState); + Logger.MarkupLine($"[green]({EmojiLiterals.CheckMark}) Done:[/] Killed minikube mount processes [blue][/]"); + } + } +} diff --git a/src/Aspirate.Commands/Actions/BindMounts/SaveBindMountsAction.cs b/src/Aspirate.Commands/Actions/BindMounts/SaveBindMountsAction.cs new file mode 100644 index 00000000..c7844b4c --- /dev/null +++ b/src/Aspirate.Commands/Actions/BindMounts/SaveBindMountsAction.cs @@ -0,0 +1,42 @@ +using System.Reflection.Metadata.Ecma335; +using Aspirate.Shared.Models.AspireManifests.Components.Common; +using Aspirate.Shared.Models.AspireManifests.Components.Common.Container; +using Microsoft.Extensions.DependencyInjection; + +namespace Aspirate.Commands.Actions.BindMounts; +public sealed class SaveBindMountsAction( + IServiceProvider serviceProvider) : BaseAction(serviceProvider) +{ + public override Task ExecuteAsync() + { + if (CurrentState.DisableMinikubeMountAction.Equals(false) || !CurrentState.DisableMinikubeMountAction.HasValue) + { + var values = new Dictionary>(); + foreach (var resource in CurrentState.AllSelectedSupportedComponents) + { + var resourceWithBindMounts = resource.Value as IResourceWithBindMounts; + + if (resourceWithBindMounts?.BindMounts.Count > 0) + { + foreach (var bindMount in resourceWithBindMounts.BindMounts) + { + if (!values.ContainsKey(bindMount.Source)) + { + values[bindMount.Source] = []; + } + values[bindMount.Source].TryAdd(bindMount.Target, null); + } + + //values.Add(resourceWithBindMounts.Name, resourceWithBindMounts.BindMounts); + } + } + + if (values.Count > 0) + { + CurrentState.BindMounts = values; + } + } + + return Task.FromResult(true); + } +} diff --git a/src/Aspirate.Commands/Actions/Manifests/RemoveManifestsFromClusterAction.cs b/src/Aspirate.Commands/Actions/Manifests/RemoveManifestsFromClusterAction.cs index 08024d91..1d8aecd6 100644 --- a/src/Aspirate.Commands/Actions/Manifests/RemoveManifestsFromClusterAction.cs +++ b/src/Aspirate.Commands/Actions/Manifests/RemoveManifestsFromClusterAction.cs @@ -5,6 +5,7 @@ public sealed class RemoveManifestsFromClusterAction( IServiceProvider serviceProvider, IFileSystem fileSystem, IDaprCliService daprCliService, + IMinikubeCliService minikubeCliService, ISecretProvider secretProvider) : BaseActionWithNonInteractiveValidation(serviceProvider) { @@ -20,6 +21,7 @@ public override async Task ExecuteAsync() CreateEmptySecretFiles(secretFiles); await kubeCtlService.RemoveManifests(CurrentState.KubeContext, CurrentState.InputPath); + Logger.MarkupLine( $"[green]({EmojiLiterals.CheckMark}) Done:[/] Deployments removed from cluster [blue]'{CurrentState.KubeContext}'[/]"); diff --git a/src/Aspirate.Commands/Commands/Apply/ApplyCommand.cs b/src/Aspirate.Commands/Commands/Apply/ApplyCommand.cs index e757fafe..24543f6a 100644 --- a/src/Aspirate.Commands/Commands/Apply/ApplyCommand.cs +++ b/src/Aspirate.Commands/Commands/Apply/ApplyCommand.cs @@ -10,5 +10,6 @@ public ApplyCommand() : base("apply", "Apply the generated kustomize manifest to AddOption(KubernetesContextOption.Instance); AddOption(SecretPasswordOption.Instance); AddOption(RollingRestartOption.Instance); + AddOption(DisableMinikubeMountActionOption.Instance); } } diff --git a/src/Aspirate.Commands/Commands/Apply/ApplyCommandHandler.cs b/src/Aspirate.Commands/Commands/Apply/ApplyCommandHandler.cs index 850e3730..7b63ef90 100644 --- a/src/Aspirate.Commands/Commands/Apply/ApplyCommandHandler.cs +++ b/src/Aspirate.Commands/Commands/Apply/ApplyCommandHandler.cs @@ -1,9 +1,12 @@ +using Aspirate.Commands.Actions.BindMounts; + namespace Aspirate.Commands.Commands.Apply; public sealed class ApplyCommandHandler(IServiceProvider serviceProvider) : BaseCommandOptionsHandler(serviceProvider) { public override Task HandleAsync(ApplyOptions optionses) => ActionExecutor + .QueueAction(nameof(ApplyMinikubeMountsAction)) .QueueAction(nameof(ApplyManifestsToClusterAction)) .ExecuteCommandsAsync(); } diff --git a/src/Aspirate.Commands/Commands/Apply/ApplyOptions.cs b/src/Aspirate.Commands/Commands/Apply/ApplyOptions.cs index c3bd7a0e..1434601e 100644 --- a/src/Aspirate.Commands/Commands/Apply/ApplyOptions.cs +++ b/src/Aspirate.Commands/Commands/Apply/ApplyOptions.cs @@ -1,8 +1,9 @@ namespace Aspirate.Commands.Commands.Apply; -public sealed class ApplyOptions : BaseCommandOptions, IKubernetesOptions, IApplyOptions +public sealed class ApplyOptions : BaseCommandOptions, IKubernetesOptions, IApplyOptions, IMinikubeOptions { public string? InputPath { get; set; } public string? KubeContext { get; set; } public bool? RollingRestart { get; set; } + public bool? DisableMinikubeMountAction { get; set; } } diff --git a/src/Aspirate.Commands/Commands/Destroy/DestroyCommand.cs b/src/Aspirate.Commands/Commands/Destroy/DestroyCommand.cs index 8233c53a..ddbc4624 100644 --- a/src/Aspirate.Commands/Commands/Destroy/DestroyCommand.cs +++ b/src/Aspirate.Commands/Commands/Destroy/DestroyCommand.cs @@ -8,5 +8,6 @@ public DestroyCommand() : base("destroy", "Removes the manifests from your clust { AddOption(InputPathOption.Instance); AddOption(KubernetesContextOption.Instance); + AddOption(DisableMinikubeMountActionOption.Instance); } } diff --git a/src/Aspirate.Commands/Commands/Destroy/DestroyCommandHandler.cs b/src/Aspirate.Commands/Commands/Destroy/DestroyCommandHandler.cs index dfc1e3d5..c9015c68 100644 --- a/src/Aspirate.Commands/Commands/Destroy/DestroyCommandHandler.cs +++ b/src/Aspirate.Commands/Commands/Destroy/DestroyCommandHandler.cs @@ -1,3 +1,5 @@ +using Aspirate.Commands.Actions.BindMounts; + namespace Aspirate.Commands.Commands.Destroy; public sealed class DestroyCommandHandler(IServiceProvider serviceProvider) : BaseCommandOptionsHandler(serviceProvider) @@ -5,5 +7,6 @@ public sealed class DestroyCommandHandler(IServiceProvider serviceProvider) : Ba public override Task HandleAsync(DestroyOptions options) => ActionExecutor .QueueAction(nameof(RemoveManifestsFromClusterAction)) + .QueueAction(nameof(KillMinikubeMountsAction)) .ExecuteCommandsAsync(); } diff --git a/src/Aspirate.Commands/Commands/Destroy/DestroyOptions.cs b/src/Aspirate.Commands/Commands/Destroy/DestroyOptions.cs index b939aa56..3d1616cc 100644 --- a/src/Aspirate.Commands/Commands/Destroy/DestroyOptions.cs +++ b/src/Aspirate.Commands/Commands/Destroy/DestroyOptions.cs @@ -1,7 +1,8 @@ namespace Aspirate.Commands.Commands.Destroy; -public sealed class DestroyOptions : BaseCommandOptions, IKubernetesOptions +public sealed class DestroyOptions : BaseCommandOptions, IKubernetesOptions, IMinikubeOptions { public string? InputPath { get; set; } public string? KubeContext { get; set; } + public bool? DisableMinikubeMountAction { get; set; } } diff --git a/src/Aspirate.Commands/Commands/Generate/GenerateCommand.cs b/src/Aspirate.Commands/Commands/Generate/GenerateCommand.cs index 3e30dc7e..1b019783 100644 --- a/src/Aspirate.Commands/Commands/Generate/GenerateCommand.cs +++ b/src/Aspirate.Commands/Commands/Generate/GenerateCommand.cs @@ -32,5 +32,6 @@ public GenerateCommand() : base("generate", "Builds, pushes containers, generate AddOption(ComposeBuildsOption.Instance); AddOption(ReplaceSecretsOption.Instance); AddOption(ParameterResourceValueOption.Instance); + AddOption(DisableMinikubeMountActionOption.Instance); } } diff --git a/src/Aspirate.Commands/Commands/Generate/GenerateCommandHandler.cs b/src/Aspirate.Commands/Commands/Generate/GenerateCommandHandler.cs index 2b2573ff..3410a747 100644 --- a/src/Aspirate.Commands/Commands/Generate/GenerateCommandHandler.cs +++ b/src/Aspirate.Commands/Commands/Generate/GenerateCommandHandler.cs @@ -1,3 +1,5 @@ +using Aspirate.Commands.Actions.BindMounts; + namespace Aspirate.Commands.Commands.Generate; public sealed class GenerateCommandHandler(IServiceProvider serviceProvider) : BaseCommandOptionsHandler(serviceProvider) @@ -30,7 +32,8 @@ private ActionExecutor BaseGenerateActionSequence() => .QueueAction(nameof(PopulateContainerDetailsForProjectsAction)) .QueueAction(nameof(BuildAndPushContainersFromProjectsAction)) .QueueAction(nameof(BuildAndPushContainersFromDockerfilesAction)) - .QueueAction(nameof(SaveSecretsAction)); + .QueueAction(nameof(SaveSecretsAction)) + .QueueAction(nameof(SaveBindMountsAction)); private ActionExecutor BaseKubernetesActionSequence() => BaseGenerateActionSequence() diff --git a/src/Aspirate.Commands/Commands/Generate/GenerateOptions.cs b/src/Aspirate.Commands/Commands/Generate/GenerateOptions.cs index 1ea40237..802dc8a1 100644 --- a/src/Aspirate.Commands/Commands/Generate/GenerateOptions.cs +++ b/src/Aspirate.Commands/Commands/Generate/GenerateOptions.cs @@ -7,7 +7,8 @@ public sealed class GenerateOptions : BaseCommandOptions, IGenerateOptions, IPrivateRegistryCredentialsOptions, IDashboardOptions, - ISecretState + ISecretState, + IMinikubeOptions { public string? ProjectPath { get; set; } public string? AspireManifest { get; set; } @@ -34,4 +35,5 @@ public sealed class GenerateOptions : BaseCommandOptions, public bool? WithPrivateRegistry { get; set; } public bool? IncludeDashboard { get; set; } public bool? ReplaceSecrets { get; set; } + public bool? DisableMinikubeMountAction { get; set; } } diff --git a/src/Aspirate.Commands/Commands/Run/RunCommand.cs b/src/Aspirate.Commands/Commands/Run/RunCommand.cs index ffe9a6c5..5b67b1ae 100644 --- a/src/Aspirate.Commands/Commands/Run/RunCommand.cs +++ b/src/Aspirate.Commands/Commands/Run/RunCommand.cs @@ -27,5 +27,6 @@ public RunCommand() : base("run", "Builds, pushes containers, and runs the curre AddOption(PrivateRegistryEmailOption.Instance); AddOption(IncludeDashboardOption.Instance); AddOption(AllowClearNamespaceOption.Instance); + AddOption(DisableMinikubeMountActionOption.Instance); } } diff --git a/src/Aspirate.Commands/Commands/Run/RunCommandHandler.cs b/src/Aspirate.Commands/Commands/Run/RunCommandHandler.cs index 1a077006..a4867f9a 100644 --- a/src/Aspirate.Commands/Commands/Run/RunCommandHandler.cs +++ b/src/Aspirate.Commands/Commands/Run/RunCommandHandler.cs @@ -1,3 +1,5 @@ +using Aspirate.Commands.Actions.BindMounts; + namespace Aspirate.Commands.Commands.Run; public sealed class RunCommandHandler(IServiceProvider serviceProvider) : BaseCommandOptionsHandler(serviceProvider) @@ -16,7 +18,9 @@ public override Task HandleAsync(RunOptions options) => .QueueAction(nameof(BuildAndPushContainersFromDockerfilesAction)) .QueueAction(nameof(AskImagePullPolicyAction)) .QueueAction(nameof(SaveSecretsAction)) + .QueueAction(nameof(SaveBindMountsAction)) .QueueAction(nameof(CustomNamespaceAction)) + .QueueAction(nameof(ApplyMinikubeMountsAction)) .QueueAction(nameof(RunKubernetesObjectsAction)) .ExecuteCommandsAsync(); } diff --git a/src/Aspirate.Commands/Commands/Run/RunOptions.cs b/src/Aspirate.Commands/Commands/Run/RunOptions.cs index bdfd2870..d0de88e7 100644 --- a/src/Aspirate.Commands/Commands/Run/RunOptions.cs +++ b/src/Aspirate.Commands/Commands/Run/RunOptions.cs @@ -5,7 +5,8 @@ public sealed class RunOptions : BaseCommandOptions, IAspireOptions, IPrivateRegistryCredentialsOptions, IDashboardOptions, - IRunOptions + IRunOptions, + IMinikubeOptions { public string? ProjectPath { get; set; } public string? AspireManifest { get; set; } @@ -26,4 +27,5 @@ public sealed class RunOptions : BaseCommandOptions, public string? PrivateRegistryEmail { get; set; } public bool? WithPrivateRegistry { get; set; } public bool? IncludeDashboard { get; set; } + public bool? DisableMinikubeMountAction { get; set; } } diff --git a/src/Aspirate.Commands/Commands/Stop/StopCommandHandler.cs b/src/Aspirate.Commands/Commands/Stop/StopCommandHandler.cs index 64a3c8ad..89ac251a 100644 --- a/src/Aspirate.Commands/Commands/Stop/StopCommandHandler.cs +++ b/src/Aspirate.Commands/Commands/Stop/StopCommandHandler.cs @@ -1,3 +1,5 @@ +using Aspirate.Commands.Actions.BindMounts; + namespace Aspirate.Commands.Commands.Stop; public sealed class StopCommandHandler(IServiceProvider serviceProvider) : BaseCommandOptionsHandler(serviceProvider) @@ -5,5 +7,6 @@ public sealed class StopCommandHandler(IServiceProvider serviceProvider) : BaseC public override Task HandleAsync(StopOptions options) => ActionExecutor .QueueAction(nameof(StopDeployedKubernetesInstanceAction)) + .QueueAction(nameof(KillMinikubeMountsAction)) .ExecuteCommandsAsync(); } diff --git a/src/Aspirate.Commands/Options/DisableMinikubeMountActionOption.cs b/src/Aspirate.Commands/Options/DisableMinikubeMountActionOption.cs new file mode 100644 index 00000000..0bb0ff50 --- /dev/null +++ b/src/Aspirate.Commands/Options/DisableMinikubeMountActionOption.cs @@ -0,0 +1,21 @@ +namespace Aspirate.Commands.Options; +public sealed class DisableMinikubeMountActionOption : BaseOption +{ + private static readonly string[] _aliases = + [ + "-dm", + "--disable-minikube-mount" + ]; + + private DisableMinikubeMountActionOption() : base(_aliases, "ASPIRATE_DISABLE_MINIKUBE_MOUNT_ACTION", null) + { + Name = nameof(IMinikubeOptions.DisableMinikubeMountAction); + Description = "Disables minikube mount actions"; + Arity = ArgumentArity.ZeroOrOne; + IsRequired = false; + } + + public static DisableMinikubeMountActionOption Instance { get; } = new(); + + public override bool IsSecret => false; +} diff --git a/src/Aspirate.Commands/ServiceCollectionExtensions.cs b/src/Aspirate.Commands/ServiceCollectionExtensions.cs index ee7415a3..ac649d3d 100644 --- a/src/Aspirate.Commands/ServiceCollectionExtensions.cs +++ b/src/Aspirate.Commands/ServiceCollectionExtensions.cs @@ -1,4 +1,6 @@ -namespace Aspirate.Commands; +using Aspirate.Commands.Actions.BindMounts; + +namespace Aspirate.Commands; /// /// Extension methods for IServiceCollection to register services for AspirateState and AspirateActions. @@ -40,11 +42,14 @@ public static IServiceCollection AddAspirateActions(this IServiceCollection serv .RegisterAction() .RegisterAction() .RegisterAction() + .RegisterAction() .RegisterAction() .RegisterAction() .RegisterAction() .RegisterAction() .RegisterAction() + .RegisterAction() + .RegisterAction() .RegisterAction(); /// diff --git a/src/Aspirate.Services/Aspirate.Services.csproj b/src/Aspirate.Services/Aspirate.Services.csproj index 5752d6a3..19fcea27 100644 --- a/src/Aspirate.Services/Aspirate.Services.csproj +++ b/src/Aspirate.Services/Aspirate.Services.csproj @@ -9,6 +9,7 @@ + diff --git a/src/Aspirate.Services/Implementations/MinikubeCliService.cs b/src/Aspirate.Services/Implementations/MinikubeCliService.cs new file mode 100644 index 00000000..5a24105f --- /dev/null +++ b/src/Aspirate.Services/Implementations/MinikubeCliService.cs @@ -0,0 +1,146 @@ +using System.Diagnostics; +using System.Security.Cryptography; +using Aspirate.Shared.Models.AspireManifests; +using Aspirate.Shared.Models.AspireManifests.Components.Common.Container; +using Aspirate.Shared.Models.AspireManifests.Components.V0.Container; +using Aspirate.Shared.Models.AspireManifests.Interfaces; +using Microsoft.Extensions.Logging; +using System.Management; + +namespace Aspirate.Services.Implementations; + +public class MinikubeCliService(IShellExecutionService shellExecutionService, IAnsiConsole logger, IServiceProvider serviceProvider) : IMinikubeCliService +{ + private string _minikubePath = "minikube"; + + private const string DefaultMountPath = "/mnt"; + private const string MountCommand = "mount"; + + public bool IsMinikubeCliInstalledOnMachine() + { + var result = shellExecutionService.IsCommandAvailable("minikube"); + + if (!result.IsAvailable) + { + return false; + } + + _minikubePath = result.FullPath; + return true; + } + + public void ActivateMinikubeMount(AspirateState state) + { + int count = 0; + foreach (var resourceWithMounts in state.BindMounts) + { + var source = resourceWithMounts.Key; + var targets = resourceWithMounts.Value; + + foreach (var target in targets.Keys) + { + if (string.IsNullOrWhiteSpace(target)) + { + logger.WriteLine("Mount target was null or empty - skipping this mount."); + continue; + } + + string args = string.Concat(MountCommand, " ", source, ":", DefaultMountPath, target); + + var startInfo = new ProcessStartInfo + { + FileName = _minikubePath, + Arguments = args, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + logger.MarkupLine($"[cyan]Executing: {_minikubePath} {args}[/]"); + + var process = Process.Start(startInfo); + + if (IsChocolateyProcess(process) && count == 0) + { + logger.MarkupLine($"[blue]minikube runs through Chocolatey shim. Process path: {process.MainModule.FileName}[/]"); + } + + targets[target] = process.Id; + } + count++; + } + logger.MarkupLine($"[green]({EmojiLiterals.CheckMark}) Done:[/] Started minikube mount processes [blue][/]"); + } + + public bool IsChocolateyProcess(Process process) + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return false; + } + + try + { + string processPath = process.MainModule?.FileName ?? "Unknown"; + + if (processPath.Contains("chocolatey")) + { + return true; + } + + return false; + } + catch (Exception ex) + { + logger.WriteLine($"Error getting minikube process path: {ex.Message}"); + } + return false; + } + + public void KillMinikubeMounts(AspirateState state) + { + foreach (var resourceWithMounts in state.BindMounts) + { + var resource = resourceWithMounts.Key; + var bindMounts = resourceWithMounts.Value; + + var processIds = bindMounts.Values; + + foreach (var nullableProcessId in processIds) + { + int processId = nullableProcessId ?? 0; + + if (processId > 0) + { + var process = Process.GetProcessById(processId); + + if (IsChocolateyProcess(process)) + { +#pragma warning disable CA1416 + using (var searcher = new ManagementObjectSearcher("SELECT ProcessId FROM Win32_Process WHERE ParentProcessId = " + processId)) + { + foreach (var obj in searcher.Get()) + { + try + { + var childProcessId = Convert.ToInt32(obj["ProcessId"]); + var childProcess = Process.GetProcessById(childProcessId); + + childProcess?.Kill(); + } + catch (Exception ex) + { + logger.WriteLine(ex.Message); + logger.WriteLine($"Could not end child process of process Id: {processId}"); + } + } + } +#pragma warning restore CA1416 + } + process.Kill(); + } + } + } + } +} diff --git a/src/Aspirate.Services/ServiceCollectionExtensions.cs b/src/Aspirate.Services/ServiceCollectionExtensions.cs index 685ae5ea..0765e03a 100644 --- a/src/Aspirate.Services/ServiceCollectionExtensions.cs +++ b/src/Aspirate.Services/ServiceCollectionExtensions.cs @@ -1,4 +1,4 @@ -namespace Aspirate.Services; +namespace Aspirate.Services; /// /// Extension methods for IServiceCollection to add Aspirate services. @@ -18,6 +18,7 @@ public static IServiceCollection AddAspirateServices(this IServiceCollection ser .AddAspirateConfigurationSupport() .AddContainerSupport() .AddDaprCliSupport() + .AddMinikubeCliSupport() .AddKubernetesSupport() .AddStateManagement() .AddSecretService() @@ -68,6 +69,10 @@ private static IServiceCollection AddDaprCliSupport(this IServiceCollection serv services .AddSingleton(); + private static IServiceCollection AddMinikubeCliSupport(this IServiceCollection services) => + services + .AddSingleton(); + private static IServiceCollection AddStateManagement(this IServiceCollection services) => services .AddSingleton(); diff --git a/src/Aspirate.Shared/Interfaces/Commands/Contracts/IApplyOptions.cs b/src/Aspirate.Shared/Interfaces/Commands/Contracts/IApplyOptions.cs index 8b3d80c9..2115b25c 100644 --- a/src/Aspirate.Shared/Interfaces/Commands/Contracts/IApplyOptions.cs +++ b/src/Aspirate.Shared/Interfaces/Commands/Contracts/IApplyOptions.cs @@ -3,4 +3,5 @@ namespace Aspirate.Shared.Interfaces.Commands.Contracts; public interface IApplyOptions { bool? RollingRestart { get; set; } + //bool? DisableMinikubeMountAction { get; set; } } diff --git a/src/Aspirate.Shared/Interfaces/Commands/Contracts/IGenerateOptions.cs b/src/Aspirate.Shared/Interfaces/Commands/Contracts/IGenerateOptions.cs index a77bf23f..fb628a59 100644 --- a/src/Aspirate.Shared/Interfaces/Commands/Contracts/IGenerateOptions.cs +++ b/src/Aspirate.Shared/Interfaces/Commands/Contracts/IGenerateOptions.cs @@ -9,4 +9,5 @@ public interface IGenerateOptions string? ImagePullPolicy { get; set; } string? OutputFormat { get; set; } List? Parameters { get; set; } + //bool? DisableMinikubeMountAction { get; set; } } diff --git a/src/Aspirate.Shared/Interfaces/Commands/Contracts/IMinikubeOptions.cs b/src/Aspirate.Shared/Interfaces/Commands/Contracts/IMinikubeOptions.cs new file mode 100644 index 00000000..409b8184 --- /dev/null +++ b/src/Aspirate.Shared/Interfaces/Commands/Contracts/IMinikubeOptions.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Aspirate.Shared.Interfaces.Commands.Contracts; +public interface IMinikubeOptions +{ + bool? DisableMinikubeMountAction { get; set; } +} diff --git a/src/Aspirate.Shared/Interfaces/Commands/Contracts/IRunOptions.cs b/src/Aspirate.Shared/Interfaces/Commands/Contracts/IRunOptions.cs index 8fee70f9..1e937e11 100644 --- a/src/Aspirate.Shared/Interfaces/Commands/Contracts/IRunOptions.cs +++ b/src/Aspirate.Shared/Interfaces/Commands/Contracts/IRunOptions.cs @@ -7,4 +7,5 @@ public interface IRunOptions bool? SkipBuild { get; set; } string? ImagePullPolicy { get; set; } string? RuntimeIdentifier { get; set; } + //bool? DisableMinikubeMountAction { get; set; } } diff --git a/src/Aspirate.Shared/Interfaces/Services/IMinikubeCliService.cs b/src/Aspirate.Shared/Interfaces/Services/IMinikubeCliService.cs new file mode 100644 index 00000000..782f95d3 --- /dev/null +++ b/src/Aspirate.Shared/Interfaces/Services/IMinikubeCliService.cs @@ -0,0 +1,8 @@ +namespace Aspirate.Shared.Interfaces.Services; + +public interface IMinikubeCliService +{ + bool IsMinikubeCliInstalledOnMachine(); + void ActivateMinikubeMount(AspirateState state); + void KillMinikubeMounts(AspirateState state); +} diff --git a/src/Aspirate.Shared/Models/Aspirate/AspirateState.cs b/src/Aspirate.Shared/Models/Aspirate/AspirateState.cs index ead31901..a93b1c97 100644 --- a/src/Aspirate.Shared/Models/Aspirate/AspirateState.cs +++ b/src/Aspirate.Shared/Models/Aspirate/AspirateState.cs @@ -129,6 +129,14 @@ public class AspirateState : [JsonPropertyName("secrets")] public SecretState? SecretState { get; set; } + [RestorableStateProperty] + [JsonPropertyName("bindMounts")] + public Dictionary>? BindMounts { get; set; } + + [RestorableStateProperty] + [JsonPropertyName("disableMinikubeMountAction")] + public bool? DisableMinikubeMountAction { get; set; } + [RestorableStateProperty] [JsonPropertyName("processAllComponents")] public bool? ProcessAllComponents { get; set; } diff --git a/src/Aspirate.Shared/Models/AspireManifests/Components/Common/BindMount.cs b/src/Aspirate.Shared/Models/AspireManifests/Components/Common/BindMount.cs index 3db896a3..3c6194e9 100644 --- a/src/Aspirate.Shared/Models/AspireManifests/Components/Common/BindMount.cs +++ b/src/Aspirate.Shared/Models/AspireManifests/Components/Common/BindMount.cs @@ -17,36 +17,21 @@ public string? Source public string? Target { get; set; } [JsonPropertyName("readOnly")] - public bool ReadOnly { get; set; } + public bool? ReadOnly { get; set; } + + [JsonPropertyName("minikubeMountProcessId")] + public int? MinikubeMountProcessId { get; set; } private static string GetVolumeHostPath(string path) { - path = path.Replace('\\', '/').Replace("//", "/"); - - string dir = Directory.GetCurrentDirectory(); - string[] subPaths = path.Split("/"); - - for (int i = 0; i < subPaths.Length; i++) + if (string.IsNullOrWhiteSpace(path)) { - string currentSub = subPaths[i]; - if (currentSub == "..") - { - dir = Directory.GetParent(dir).FullName; - } - else - { - if (dir == Directory.GetDirectoryRoot(dir)) - { - dir += currentSub; - } - else - { - dir = dir + Path.DirectorySeparatorChar + currentSub; - } - } + return ""; } - return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "/run/desktop/mnt/host/" + dir.ToLower().Replace(":", "").Replace('\\', '/') : dir; + string currentDir = Directory.GetCurrentDirectory(); + string resolvedPath = Path.GetFullPath(Path.Combine(currentDir, path)); + + return resolvedPath; } } - diff --git a/src/Aspirate.Shared/Models/AspireManifests/Interfaces/IResourceWithBindMounts.cs b/src/Aspirate.Shared/Models/AspireManifests/Interfaces/IResourceWithBindMounts.cs index 517f8821..64745fc1 100644 --- a/src/Aspirate.Shared/Models/AspireManifests/Interfaces/IResourceWithBindMounts.cs +++ b/src/Aspirate.Shared/Models/AspireManifests/Interfaces/IResourceWithBindMounts.cs @@ -1,6 +1,6 @@ namespace Aspirate.Shared.Models.AspireManifests.Interfaces; -public interface IResourceWithBindMounts +public interface IResourceWithBindMounts : IResource { List? BindMounts { get; set; } }