diff --git a/src/.idea/.idea.port/.idea/indexLayout.xml b/src/.idea/.idea.port/.idea/indexLayout.xml index 556fba7..ed6ed36 100644 --- a/src/.idea/.idea.port/.idea/indexLayout.xml +++ b/src/.idea/.idea.port/.idea/indexLayout.xml @@ -7,4 +7,4 @@ - \ No newline at end of file + diff --git a/src/.idea/.idea.port/.idea/vcs.xml b/src/.idea/.idea.port/.idea/vcs.xml index 6c0b863..54e4b96 100644 --- a/src/.idea/.idea.port/.idea/vcs.xml +++ b/src/.idea/.idea.port/.idea/vcs.xml @@ -3,4 +3,4 @@ - \ No newline at end of file + diff --git a/src/.idea/.idea.src.dir/.idea/indexLayout.xml b/src/.idea/.idea.src.dir/.idea/indexLayout.xml index 7b08163..67b8dc9 100644 --- a/src/.idea/.idea.src.dir/.idea/indexLayout.xml +++ b/src/.idea/.idea.src.dir/.idea/indexLayout.xml @@ -5,4 +5,4 @@ - \ No newline at end of file + diff --git a/src/.idea/.idea.src.dir/.idea/projectSettingsUpdater.xml b/src/.idea/.idea.src.dir/.idea/projectSettingsUpdater.xml index 4bb9f4d..2d2b171 100644 --- a/src/.idea/.idea.src.dir/.idea/projectSettingsUpdater.xml +++ b/src/.idea/.idea.src.dir/.idea/projectSettingsUpdater.xml @@ -3,4 +3,4 @@ - \ No newline at end of file + diff --git a/src/CommandChainDetector.cs b/src/CommandChainDetector.cs new file mode 100644 index 0000000..a378057 --- /dev/null +++ b/src/CommandChainDetector.cs @@ -0,0 +1,52 @@ +namespace port; + +/// +/// Detects if the current command is part of a command chain by analyzing command execution context. +/// This helps determine when to suppress intermediate output like listing current state. +/// +internal class CommandChainDetector : ICommandChainDetector +{ + private readonly Lazy _shouldDisplayOutput; + + public CommandChainDetector() + { + _shouldDisplayOutput = new Lazy(DetectShouldDisplayOutput); + } + + public bool ShouldDisplayOutput() => _shouldDisplayOutput.Value; + + private static bool DetectShouldDisplayOutput() + { + try + { + // Get the current command being executed + var args = Environment.GetCommandLineArgs(); + if (args.Length < 2) + return true; + + var command = args[1].ToLowerInvariant(); + + // Commands that typically end chains and should show output by default + var finalCommands = new[] { "prune", "pr", "list", "ls" }; + + // Commands that are typically intermediate in chains + var intermediateCommands = new[] { "pull", "p", "run", "r" }; + + // Simple heuristic: if this is a command that typically ends chains, show output + // Otherwise, for intermediate commands, we suppress by default (conservative approach) + if (finalCommands.Contains(command)) + return true; + + if (intermediateCommands.Contains(command)) + return false; + + // For other commands (commit, reset, stop, remove, etc.), show output by default + return true; + } + catch + { + // If we can't determine the chain status, default to showing output + return true; + } + } +} diff --git a/src/Commands/Commit/CommitCliCommand.cs b/src/Commands/Commit/CommitCliCommand.cs index bb381b6..cc66c60 100644 --- a/src/Commands/Commit/CommitCliCommand.cs +++ b/src/Commands/Commit/CommitCliCommand.cs @@ -14,83 +14,108 @@ internal class CommitCliCommand( IGetDigestsByIdQuery getDigestsByIdQuery, IGetContainersQuery getContainersQuery, IStopAndRemoveContainerCommand stopAndRemoveContainerCommand, - ListCliCommand listCliCommand) - : AsyncCommand + ConditionalListCliCommand conditionalListCliCommand +) : AsyncCommand { public override async Task ExecuteAsync(CommandContext context, CommitSettings settings) { - var container = await GetContainerAsync(settings) ?? - throw new InvalidOperationException("No running container found"); + var container = + await GetContainerAsync(settings) + ?? throw new InvalidOperationException("No running container found"); - await Spinner.StartAsync("Committing container", async ctx => - { - string newTag; - string imageName; - string tagPrefix; - if (settings.Overwrite) - { - newTag = container.ImageTag ?? - throw new InvalidOperationException( - "When using --overwrite, container must have an image tag"); - imageName = container.ImageIdentifier; - tagPrefix = container.TagPrefix; - } - else + await Spinner.StartAsync( + "Committing container", + async ctx => { - var tag = settings.Tag ?? $"{DateTime.Now:yyyyMMddhhmmss}"; - (imageName, tagPrefix, newTag) = await GetNewTagAsync(container, tag); - } - - - ctx.Status = $"Looking for existing container named '{container.ContainerName}'"; - var containerWithSameTag = await getContainersQuery - .QueryByContainerIdentifierAndTagAsync(container.ContainerIdentifier, newTag) - .ToListAsync(); - - ctx.Status = $"Creating image from running container '{container.ContainerName}'"; - newTag = await createImageFromContainerCommand.ExecuteAsync(container, imageName, tagPrefix, newTag); - - ctx.Status = $"Removing containers named '{container.ContainerName}'"; - await Task.WhenAll(containerWithSameTag.Select(async container1 => - await stopAndRemoveContainerCommand.ExecuteAsync(container1.Id))); - - if (settings.Overwrite) - { - if (newTag == null) - throw new InvalidOperationException("newTag is null"); - - if (container.ImageTag == null) - throw new InvalidOperationException( - "Switch argument not supported when creating image from untagged container"); - - ctx.Status = "Launching new image"; - var id = await createContainerCommand.ExecuteAsync(container, tagPrefix, newTag); - await runContainerCommand.ExecuteAsync(id); - } - else if (settings.Switch) - { - if (newTag == null) - throw new InvalidOperationException("newTag is null"); - - if (container.ImageTag == null) - throw new InvalidOperationException( - "Switch argument not supported when creating image from untagged container"); - - ctx.Status = $"Stopping running container '{container.ContainerName}'"; - await stopContainerCommand.ExecuteAsync(container.Id); - - ctx.Status = "Launching new image"; - var id = await createContainerCommand.ExecuteAsync(container, tagPrefix, newTag); - await runContainerCommand.ExecuteAsync(id); + string newTag; + string imageName; + string tagPrefix; + if (settings.Overwrite) + { + newTag = + container.ImageTag + ?? throw new InvalidOperationException( + "When using --overwrite, container must have an image tag" + ); + imageName = container.ImageIdentifier; + tagPrefix = container.TagPrefix; + } + else + { + var tag = settings.Tag ?? $"{DateTime.Now:yyyyMMddhhmmss}"; + (imageName, tagPrefix, newTag) = await GetNewTagAsync(container, tag); + } + + ctx.Status = $"Looking for existing container named '{container.ContainerName}'"; + var containerWithSameTag = await getContainersQuery + .QueryByContainerIdentifierAndTagAsync(container.ContainerIdentifier, newTag) + .ToListAsync(); + + ctx.Status = $"Creating image from running container '{container.ContainerName}'"; + newTag = await createImageFromContainerCommand.ExecuteAsync( + container, + imageName, + tagPrefix, + newTag + ); + + ctx.Status = $"Removing containers named '{container.ContainerName}'"; + await Task.WhenAll( + containerWithSameTag.Select(async container1 => + await stopAndRemoveContainerCommand.ExecuteAsync(container1.Id) + ) + ); + + if (settings.Overwrite) + { + if (newTag == null) + throw new InvalidOperationException("newTag is null"); + + if (container.ImageTag == null) + throw new InvalidOperationException( + "Switch argument not supported when creating image from untagged container" + ); + + ctx.Status = "Launching new image"; + var id = await createContainerCommand.ExecuteAsync( + container, + tagPrefix, + newTag + ); + await runContainerCommand.ExecuteAsync(id); + } + else if (settings.Switch) + { + if (newTag == null) + throw new InvalidOperationException("newTag is null"); + + if (container.ImageTag == null) + throw new InvalidOperationException( + "Switch argument not supported when creating image from untagged container" + ); + + ctx.Status = $"Stopping running container '{container.ContainerName}'"; + await stopContainerCommand.ExecuteAsync(container.Id); + + ctx.Status = "Launching new image"; + var id = await createContainerCommand.ExecuteAsync( + container, + tagPrefix, + newTag + ); + await runContainerCommand.ExecuteAsync(id); + } } - }); + ); - await listCliCommand.ExecuteAsync(); + await conditionalListCliCommand.ExecuteAsync(); return 0; } - private async Task<(string imageName, string tagPrefix, string newTag)> GetNewTagAsync(Container container, - string tag) + private async Task<(string imageName, string tagPrefix, string newTag)> GetNewTagAsync( + Container container, + string tag + ) { var image = await getImageQuery.QueryAsync(container.ImageIdentifier, container.ImageTag); string imageName; @@ -101,7 +126,8 @@ await Task.WhenAll(containerWithSameTag.Select(async container1 => var digest = digests?.SingleOrDefault(); if (digest == null || !DigestHelper.TryGetImageNameAndId(digest, out var nameNameAndId)) throw new InvalidOperationException( - $"Unable to determine image name from running container '{container.ContainerName}'"); + $"Unable to determine image name from running container '{container.ContainerName}'" + ); imageName = nameNameAndId.imageName; } else @@ -114,7 +140,8 @@ await Task.WhenAll(containerWithSameTag.Select(async container1 => var tagPrefix = container.TagPrefix; var newTag = baseTag == null ? tag : $"{tagPrefix}{baseTag}-{tag}"; - if (newTag.Contains('.')) throw new ArgumentException("only [a-zA-Z0-9][a-zA-Z0-9_-] are allowed"); + if (newTag.Contains('.')) + throw new ArgumentException("only [a-zA-Z0-9][a-zA-Z0-9_-] are allowed"); return (imageName, tagPrefix, newTag); } @@ -129,4 +156,4 @@ await Task.WhenAll(containerWithSameTag.Select(async container1 => var identifier = containerNamePrompt.GetIdentifierOfContainerFromUser(containers, "commit"); return containers.SingleOrDefault(c => c.ContainerName == identifier); } -} \ No newline at end of file +} diff --git a/src/Commands/Commit/CommitSettings.cs b/src/Commands/Commit/CommitSettings.cs index a4dad5f..7a9c411 100644 --- a/src/Commands/Commit/CommitSettings.cs +++ b/src/Commands/Commit/CommitSettings.cs @@ -7,12 +7,12 @@ public class CommitSettings : CommandSettings, IContainerIdentifierSettings [CommandArgument(0, "[ContainerIdentifier]")] public string? ContainerIdentifier { get; set; } - [CommandOption("-t|--tag")] + [CommandOption("-t|--tag")] public string? Tag { get; set; } - [CommandOption("-s|--switch")] + [CommandOption("-s|--switch")] public bool Switch { get; set; } - [CommandOption("-o|--overwrite")] + [CommandOption("-o|--overwrite")] public bool Overwrite { get; set; } -} \ No newline at end of file +} diff --git a/src/Commands/Commit/CreateImageFromContainerCommand.cs b/src/Commands/Commit/CreateImageFromContainerCommand.cs index f72456c..f7a9139 100644 --- a/src/Commands/Commit/CreateImageFromContainerCommand.cs +++ b/src/Commands/Commit/CreateImageFromContainerCommand.cs @@ -3,27 +3,35 @@ namespace port.Commands.Commit; -internal class CreateImageFromContainerCommand(IDockerClient dockerClient) : ICreateImageFromContainerCommand +internal class CreateImageFromContainerCommand(IDockerClient dockerClient) + : ICreateImageFromContainerCommand { - public async Task ExecuteAsync(Container container, string imageName, string tagPrefix, string newTag) + public async Task ExecuteAsync( + Container container, + string imageName, + string tagPrefix, + string newTag + ) { var labels = new Dictionary(); var identifier = container.GetLabel(Constants.IdentifierLabel); - if (identifier is not null) labels.Add(Constants.IdentifierLabel, identifier); + if (identifier is not null) + labels.Add(Constants.IdentifierLabel, identifier); var baseTag = container.GetLabel(Constants.BaseTagLabel); - if (baseTag is not null) labels.Add(Constants.BaseTagLabel, baseTag); + if (baseTag is not null) + labels.Add(Constants.BaseTagLabel, baseTag); labels.Add(Constants.TagPrefix, tagPrefix); - if (baseTag == newTag) throw new InvalidOperationException("Can not overwrite base tags"); - await dockerClient.Images.CommitContainerChangesAsync(new CommitContainerChangesParameters - { - ContainerID = container.Id, - RepositoryName = imageName, - Tag = newTag, - Config = new Docker.DotNet.Models.Config + if (baseTag == newTag) + throw new InvalidOperationException("Can not overwrite base tags"); + await dockerClient.Images.CommitContainerChangesAsync( + new CommitContainerChangesParameters { - Labels = labels + ContainerID = container.Id, + RepositoryName = imageName, + Tag = newTag, + Config = new Docker.DotNet.Models.Config { Labels = labels }, } - }); + ); return newTag; } -} \ No newline at end of file +} diff --git a/src/Commands/Commit/GetDigestsByIdQuery.cs b/src/Commands/Commit/GetDigestsByIdQuery.cs index 227c396..d7b931f 100644 --- a/src/Commands/Commit/GetDigestsByIdQuery.cs +++ b/src/Commands/Commit/GetDigestsByIdQuery.cs @@ -16,11 +16,10 @@ public GetDigestsByIdQuery(IDockerClient dockerClient) { var parameters = new ImagesListParameters { - Filters = new Dictionary>() + Filters = new Dictionary>(), }; var imagesListResponses = await _dockerClient.Images.ListImagesAsync(parameters); - var imagesListResponse = imagesListResponses - .SingleOrDefault(e => e.ID == imageId); + var imagesListResponse = imagesListResponses.SingleOrDefault(e => e.ID == imageId); return imagesListResponse?.RepoDigests; } -} \ No newline at end of file +} diff --git a/src/Commands/Commit/ICreateImageFromContainerCommand.cs b/src/Commands/Commit/ICreateImageFromContainerCommand.cs index c7e5219..3ee0320 100644 --- a/src/Commands/Commit/ICreateImageFromContainerCommand.cs +++ b/src/Commands/Commit/ICreateImageFromContainerCommand.cs @@ -2,5 +2,10 @@ namespace port.Commands.Commit; public interface ICreateImageFromContainerCommand { - Task ExecuteAsync(Container container, string imageName, string tagPrefix, string newTag); -} \ No newline at end of file + Task ExecuteAsync( + Container container, + string imageName, + string tagPrefix, + string newTag + ); +} diff --git a/src/Commands/Commit/IGetDigestsByIdQuery.cs b/src/Commands/Commit/IGetDigestsByIdQuery.cs index d9ed986..9eb6a46 100644 --- a/src/Commands/Commit/IGetDigestsByIdQuery.cs +++ b/src/Commands/Commit/IGetDigestsByIdQuery.cs @@ -3,4 +3,4 @@ namespace port.Commands.Commit; internal interface IGetDigestsByIdQuery { Task?> QueryAsync(string imageId); -} \ No newline at end of file +} diff --git a/src/Commands/Config/ConfigCliCommand.cs b/src/Commands/Config/ConfigCliCommand.cs index 5725fbf..4390fb6 100644 --- a/src/Commands/Config/ConfigCliCommand.cs +++ b/src/Commands/Config/ConfigCliCommand.cs @@ -22,5 +22,6 @@ public override int Execute(CommandContext context, ConfigSettings settings) return 0; } - private static string FormatAsLink(string caption, string url) => $"\u001B]8;;{url}\a{caption}\u001B]8;;\a"; -} \ No newline at end of file + private static string FormatAsLink(string caption, string url) => + $"\u001B]8;;{url}\a{caption}\u001B]8;;\a"; +} diff --git a/src/Commands/Config/ConfigSettings.cs b/src/Commands/Config/ConfigSettings.cs index cce7f5b..ce9e2d5 100644 --- a/src/Commands/Config/ConfigSettings.cs +++ b/src/Commands/Config/ConfigSettings.cs @@ -6,4 +6,4 @@ internal class ConfigSettings : CommandSettings { [CommandOption("-o|--open")] public bool Open { get; set; } = false; -} \ No newline at end of file +} diff --git a/src/Commands/List/ListCliCommand.cs b/src/Commands/List/ListCliCommand.cs index 8d79b74..4947767 100644 --- a/src/Commands/List/ListCliCommand.cs +++ b/src/Commands/List/ListCliCommand.cs @@ -14,8 +14,10 @@ public ListCliCommand(IAllImagesQuery allImagesQuery) public override async Task ExecuteAsync(CommandContext _, ListSettings settings) { - var textsGroups = await Spinner.StartAsync("Loading images", - async _ => await CreateImageTree(settings.ImageIdentifier).ToListAsync()); + var textsGroups = await Spinner.StartAsync( + "Loading images", + async _ => await CreateImageTree(settings.ImageIdentifier).ToListAsync() + ); AnsiConsole.WriteLine(); foreach (var text in textsGroups.SelectMany(texts => texts)) { @@ -27,7 +29,10 @@ public override async Task ExecuteAsync(CommandContext _, ListSettings sett public async Task ExecuteAsync() { - var textsGroups = await Spinner.StartAsync("Loading images", async _ => await CreateImageTree().ToListAsync()); + var textsGroups = await Spinner.StartAsync( + "Loading images", + async _ => await CreateImageTree().ToListAsync() + ); AnsiConsole.WriteLine(); foreach (var text in textsGroups.SelectMany(texts => texts)) { @@ -37,16 +42,20 @@ public async Task ExecuteAsync() private async IAsyncEnumerable> CreateImageTree(string? imageIdentifier = default) { - var imageGroups = (await _allImagesQuery.QueryAsync().ToListAsync()).Where(e => - imageIdentifier == null || e.Identifier == imageIdentifier) - .OrderBy(i => i.Identifier).ToList(); - var lengths = TagTextBuilder.GetLengths(imageGroups.SelectMany(imageGroup => imageGroup.Images)); + var imageGroups = (await _allImagesQuery.QueryAsync().ToListAsync()) + .Where(e => imageIdentifier == null || e.Identifier == imageIdentifier) + .OrderBy(i => i.Identifier) + .ToList(); + var lengths = TagTextBuilder.GetLengths( + imageGroups.SelectMany(imageGroup => imageGroup.Images) + ); foreach (var imageGroup in imageGroups) { - yield return imageGroup.Images.Where(e => e.Tag != null) + yield return imageGroup + .Images.Where(e => e.Tag != null) .OrderBy(e => e.Tag) .Select(image => TagTextBuilder.BuildTagText(image, lengths)) .ToList(); } } -} \ No newline at end of file +} diff --git a/src/Commands/List/ListSettings.cs b/src/Commands/List/ListSettings.cs index 356ebff..2c1a845 100644 --- a/src/Commands/List/ListSettings.cs +++ b/src/Commands/List/ListSettings.cs @@ -4,6 +4,6 @@ namespace port.Commands.List; public class ListSettings : CommandSettings, IImageIdentifierSettings { - [CommandArgument(0, "[ImageIdentifier]")] + [CommandArgument(0, "[ImageIdentifier]")] public string? ImageIdentifier { get; set; } -} \ No newline at end of file +} diff --git a/src/Commands/Prune/PruneCliCommand.cs b/src/Commands/Prune/PruneCliCommand.cs index eefe61a..b3f6d1b 100644 --- a/src/Commands/Prune/PruneCliCommand.cs +++ b/src/Commands/Prune/PruneCliCommand.cs @@ -11,18 +11,23 @@ internal class PruneCliCommand : AsyncCommand private readonly port.Config.Config _config; private readonly IAllImagesQuery _allImagesQuery; private readonly IRemoveImagesCliDependentCommand _removeImagesCliDependentCommand; - private readonly ListCliCommand _listCliCommand; + private readonly ConditionalListCliCommand _conditionalListCliCommand; - public PruneCliCommand(IImageIdentifierAndTagEvaluator imageIdentifierAndTagEvaluator, - IGetImageIdQuery getImageIdQuery, port.Config.Config config, IAllImagesQuery allImagesQuery, - IRemoveImagesCliDependentCommand removeImagesCliDependentCommand, ListCliCommand listCliCommand) + public PruneCliCommand( + IImageIdentifierAndTagEvaluator imageIdentifierAndTagEvaluator, + IGetImageIdQuery getImageIdQuery, + port.Config.Config config, + IAllImagesQuery allImagesQuery, + IRemoveImagesCliDependentCommand removeImagesCliDependentCommand, + ConditionalListCliCommand conditionalListCliCommand + ) { _imageIdentifierAndTagEvaluator = imageIdentifierAndTagEvaluator; _getImageIdQuery = getImageIdQuery; _config = config; _allImagesQuery = allImagesQuery; _removeImagesCliDependentCommand = removeImagesCliDependentCommand; - _listCliCommand = listCliCommand; + _conditionalListCliCommand = conditionalListCliCommand; } public override async Task ExecuteAsync(CommandContext context, PruneSettings settings) @@ -30,16 +35,23 @@ public override async Task ExecuteAsync(CommandContext context, PruneSettin var identifiers = GetIdentifiersAsync(settings); await foreach (var identifier in identifiers) { - var result = await Spinner.StartAsync($"Removing untagged images for identifier '{identifier}'", - ctx => RemoveUntaggedImagesAsync(identifier, ctx)); - foreach (var imageRemovalResult in result.Where(imageRemovalResult => !imageRemovalResult.Successful)) + var result = await Spinner.StartAsync( + $"Removing untagged images for identifier '{identifier}'", + ctx => RemoveUntaggedImagesAsync(identifier, ctx) + ); + foreach ( + var imageRemovalResult in result.Where(imageRemovalResult => + !imageRemovalResult.Successful + ) + ) { AnsiConsole.MarkupLine( - $"[orange3]Unable to removed image with id '{imageRemovalResult.ImageId}'[/] because it has dependent child images"); + $"[orange3]Unable to removed image with id '{imageRemovalResult.ImageId}'[/] because it has dependent child images" + ); } } - await _listCliCommand.ExecuteAsync(); + await _conditionalListCliCommand.ExecuteAsync(); return 0; } @@ -48,26 +60,32 @@ private async IAsyncEnumerable GetIdentifiersAsync(IImageIdentifierSetti { if (settings.ImageIdentifier != null) { - yield return _imageIdentifierAndTagEvaluator.Evaluate(settings.ImageIdentifier).identifier; + yield return _imageIdentifierAndTagEvaluator + .Evaluate(settings.ImageIdentifier) + .identifier; } - await foreach (var identifier in _allImagesQuery - .QueryAsync() - .Where(e => e.Images.Any(i => i.Tag == null)) - .Select(e => e.Identifier)) + await foreach ( + var identifier in _allImagesQuery + .QueryAsync() + .Where(e => e.Images.Any(i => i.Tag == null)) + .Select(e => e.Identifier) + ) { yield return identifier; } } - private async Task> RemoveUntaggedImagesAsync(string identifier, StatusContext ctx) + private async Task> RemoveUntaggedImagesAsync( + string identifier, + StatusContext ctx + ) { var imageConfig = _config.GetImageConfigByIdentifier(identifier); var imageName = imageConfig.ImageName; var imageIds = (await _getImageIdQuery.QueryAsync(imageName, null)).ToList(); if (!imageIds.Any()) - throw new InvalidOperationException( - "No images to remove found".EscapeMarkup()); + throw new InvalidOperationException("No images to remove found".EscapeMarkup()); return await _removeImagesCliDependentCommand.ExecuteAsync(imageIds, ctx); } -} \ No newline at end of file +} diff --git a/src/Commands/Prune/PruneSettings.cs b/src/Commands/Prune/PruneSettings.cs index a6a0383..613d6e6 100644 --- a/src/Commands/Prune/PruneSettings.cs +++ b/src/Commands/Prune/PruneSettings.cs @@ -6,4 +6,4 @@ internal class PruneSettings : CommandSettings, IImageIdentifierSettings { [CommandArgument(0, "[ImageIdentifier]")] public string? ImageIdentifier { get; set; } -} \ No newline at end of file +} diff --git a/src/Commands/Pull/PullCliCommand.cs b/src/Commands/Pull/PullCliCommand.cs index 33c9bde..c991877 100644 --- a/src/Commands/Pull/PullCliCommand.cs +++ b/src/Commands/Pull/PullCliCommand.cs @@ -2,28 +2,34 @@ namespace port.Commands.Pull; -public class PullCliCommand( +internal class PullCliCommand( IImageIdentifierPrompt imageIdentifierPrompt, port.Config.Config config, IImageIdentifierAndTagEvaluator imageIdentifierAndTagEvaluator, - ICreateImageCliChildCommand createImageCliChildCommand) - : AsyncCommand + ICreateImageCliChildCommand createImageCliChildCommand, + ConditionalListCliCommand conditionalListCliCommand +) : AsyncCommand { public override async Task ExecuteAsync(CommandContext context, PullSettings settings) { var (identifier, tag) = await GetBaseIdentifierAndTagAsync(settings); await PullImageAsync(identifier, tag); + await conditionalListCliCommand.ExecuteAsync(); return 0; } - private async Task<(string identifier, string? tag)> GetBaseIdentifierAndTagAsync(IImageIdentifierSettings settings) + private async Task<(string identifier, string? tag)> GetBaseIdentifierAndTagAsync( + IImageIdentifierSettings settings + ) { if (settings.ImageIdentifier != null) { return imageIdentifierAndTagEvaluator.Evaluate(settings.ImageIdentifier); } - var identifierAndTag = await imageIdentifierPrompt.GetBaseIdentifierAndTagFromUserAsync("pull"); + var identifierAndTag = await imageIdentifierPrompt.GetBaseIdentifierAndTagFromUserAsync( + "pull" + ); return (identifierAndTag.identifier, identifierAndTag.tag); } @@ -32,11 +38,13 @@ private async Task PullImageAsync(string identifier, string? tag) var imageConfig = config.GetImageConfigByIdentifier(identifier); if (imageConfig == null) { - throw new ArgumentException($"There is no config defined for identifier '{identifier}'", - nameof(identifier)); + throw new ArgumentException( + $"There is no config defined for identifier '{identifier}'", + nameof(identifier) + ); } var imageName = imageConfig.ImageName; await createImageCliChildCommand.ExecuteAsync(imageName, tag); } -} \ No newline at end of file +} diff --git a/src/Commands/Pull/PullSettings.cs b/src/Commands/Pull/PullSettings.cs index 0d2854f..0407f00 100644 --- a/src/Commands/Pull/PullSettings.cs +++ b/src/Commands/Pull/PullSettings.cs @@ -4,6 +4,6 @@ namespace port.Commands.Pull; public class PullSettings : CommandSettings, IImageIdentifierSettings { - [CommandArgument(0, "[ImageIdentifier]")] + [CommandArgument(0, "[ImageIdentifier]")] public string? ImageIdentifier { get; set; } -} \ No newline at end of file +} diff --git a/src/Commands/Remove/RemoveCliCommand.cs b/src/Commands/Remove/RemoveCliCommand.cs index a478c71..a2f7281 100644 --- a/src/Commands/Remove/RemoveCliCommand.cs +++ b/src/Commands/Remove/RemoveCliCommand.cs @@ -12,12 +12,17 @@ internal class RemoveCliCommand : AsyncCommand private readonly IImageIdentifierAndTagEvaluator _imageIdentifierAndTagEvaluator; private readonly IAllImagesQuery _allImagesQuery; private readonly IRemoveImagesCliDependentCommand _removeImagesCliDependentCommand; - private readonly ListCliCommand _listCliCommand; + private readonly ConditionalListCliCommand _conditionalListCliCommand; - public RemoveCliCommand(IImageIdentifierPrompt imageIdentifierPrompt, port.Config.Config config, - IImageIdentifierAndTagEvaluator imageIdentifierAndTagEvaluator, IGetImageIdQuery getImageIdQuery, - IAllImagesQuery allImagesQuery, IRemoveImagesCliDependentCommand removeImagesCliDependentCommand, - ListCliCommand listCliCommand) + public RemoveCliCommand( + IImageIdentifierPrompt imageIdentifierPrompt, + port.Config.Config config, + IImageIdentifierAndTagEvaluator imageIdentifierAndTagEvaluator, + IGetImageIdQuery getImageIdQuery, + IAllImagesQuery allImagesQuery, + IRemoveImagesCliDependentCommand removeImagesCliDependentCommand, + ConditionalListCliCommand conditionalListCliCommand + ) { _imageIdentifierPrompt = imageIdentifierPrompt; _config = config; @@ -25,77 +30,98 @@ public RemoveCliCommand(IImageIdentifierPrompt imageIdentifierPrompt, port.Confi _getImageIdQuery = getImageIdQuery; _allImagesQuery = allImagesQuery; _removeImagesCliDependentCommand = removeImagesCliDependentCommand; - _listCliCommand = listCliCommand; + _conditionalListCliCommand = conditionalListCliCommand; } public override async Task ExecuteAsync(CommandContext context, RemoveSettings settings) { var (identifier, tag) = await GetIdentifierAndTagAsync(settings); - var result = await Spinner.StartAsync($"Removing {ImageNameHelper.BuildImageName(identifier, tag)}", - async ctx => - { - var imageConfig = _config.GetImageConfigByIdentifier(identifier); - var imageName = imageConfig.ImageName; - var initialImageIds = new List(); - if (tag is not null && !imageConfig.ImageTags.Contains(tag)) + var result = await Spinner + .StartAsync( + $"Removing {ImageNameHelper.BuildImageName(identifier, tag)}", + async ctx => { - initialImageIds.AddRange(await _getImageIdQuery.QueryAsync(imageName, $"{TagPrefixHelper.GetTagPrefix(identifier)}{tag}")); - } - - initialImageIds.AddRange(await _getImageIdQuery.QueryAsync(imageName, tag)); + var imageConfig = _config.GetImageConfigByIdentifier(identifier); + var imageName = imageConfig.ImageName; + var initialImageIds = new List(); + if (tag is not null && !imageConfig.ImageTags.Contains(tag)) + { + initialImageIds.AddRange( + await _getImageIdQuery.QueryAsync( + imageName, + $"{TagPrefixHelper.GetTagPrefix(identifier)}{tag}" + ) + ); + } - var imageIds = new List(); - if (settings.Recursive) - { - var images = (await _allImagesQuery.QueryAllImagesWithParentAsync().ToListAsync()) - .Where(e => e is { Id: not null, ParentId: not null }) - .ToList(); - var imageIdsToAnalyze = initialImageIds.ToHashSet(); - while (imageIdsToAnalyze.Count != 0) + initialImageIds.AddRange(await _getImageIdQuery.QueryAsync(imageName, tag)); + + var imageIds = new List(); + if (settings.Recursive) { - imageIds.AddRange(imageIdsToAnalyze); - var analyze = imageIdsToAnalyze; - var childImageIds = images - .Where(e => analyze.Contains(e.ParentId)) - .Select(e => e.Id) - .ToHashSet(); - imageIdsToAnalyze = childImageIds; + var images = ( + await _allImagesQuery.QueryAllImagesWithParentAsync().ToListAsync() + ) + .Where(e => e is { Id: not null, ParentId: not null }) + .ToList(); + var imageIdsToAnalyze = initialImageIds.ToHashSet(); + while (imageIdsToAnalyze.Count != 0) + { + imageIds.AddRange(imageIdsToAnalyze); + var analyze = imageIdsToAnalyze; + var childImageIds = images + .Where(e => analyze.Contains(e.ParentId)) + .Select(e => e.Id) + .ToHashSet(); + imageIdsToAnalyze = childImageIds; + } + + imageIds.Reverse(); + } + else + { + imageIds = initialImageIds.ToList(); } - imageIds.Reverse(); + if (imageIds.Count == 0) + throw new InvalidOperationException( + "No images to remove found".EscapeMarkup() + ); + return _removeImagesCliDependentCommand.ExecuteAsync(imageIds, ctx); } - else - { - imageIds = initialImageIds.ToList(); - } - - if (imageIds.Count == 0) - throw new InvalidOperationException( - "No images to remove found".EscapeMarkup()); - return _removeImagesCliDependentCommand.ExecuteAsync(imageIds, ctx); - }).Unwrap(); - foreach (var imageRemovalResult in result.Where(imageRemovalResult => !imageRemovalResult.Successful)) + ) + .Unwrap(); + foreach ( + var imageRemovalResult in result.Where(imageRemovalResult => + !imageRemovalResult.Successful + ) + ) { AnsiConsole.MarkupLine( - $"[orange3]Unable to removed image with id '{imageRemovalResult.ImageId}'[/] because it has dependent children"); + $"[orange3]Unable to removed image with id '{imageRemovalResult.ImageId}'[/] because it has dependent children" + ); if (settings.Recursive) AnsiConsole.MarkupLine( - "That may be because an child image is based on an [red]unknown image[/] which can not be removed automatically, manually remove it and try again"); + "That may be because an child image is based on an [red]unknown image[/] which can not be removed automatically, manually remove it and try again" + ); } - await _listCliCommand.ExecuteAsync(); + await _conditionalListCliCommand.ExecuteAsync(); return 0; } - private async Task<(string identifier, string? tag)> GetIdentifierAndTagAsync(IImageIdentifierSettings settings) + private async Task<(string identifier, string? tag)> GetIdentifierAndTagAsync( + IImageIdentifierSettings settings + ) { if (settings.ImageIdentifier != null) { return _imageIdentifierAndTagEvaluator.Evaluate(settings.ImageIdentifier); } - var identifierAndTag = await _imageIdentifierPrompt.GetDownloadedIdentifierAndTagFromUserAsync("remove"); + var identifierAndTag = + await _imageIdentifierPrompt.GetDownloadedIdentifierAndTagFromUserAsync("remove"); return (identifierAndTag.identifier, identifierAndTag.tag); } -} \ No newline at end of file +} diff --git a/src/Commands/Remove/RemoveSettings.cs b/src/Commands/Remove/RemoveSettings.cs index 0a790b9..0aebe76 100644 --- a/src/Commands/Remove/RemoveSettings.cs +++ b/src/Commands/Remove/RemoveSettings.cs @@ -4,9 +4,9 @@ namespace port.Commands.Remove; public class RemoveSettings : CommandSettings, IImageIdentifierSettings { - [CommandArgument(0, "[ImageIdentifier]")] + [CommandArgument(0, "[ImageIdentifier]")] public string? ImageIdentifier { get; set; } - + [CommandOption("-r|--recursive")] public bool Recursive { get; set; } -} \ No newline at end of file +} diff --git a/src/Commands/Reset/ResetCliCommand.cs b/src/Commands/Reset/ResetCliCommand.cs index a35da89..657f1ff 100644 --- a/src/Commands/Reset/ResetCliCommand.cs +++ b/src/Commands/Reset/ResetCliCommand.cs @@ -9,8 +9,8 @@ internal class ResetCliCommand( ICreateContainerCommand createContainerCommand, IRunContainerCommand runContainerCommand, IContainerNamePrompt containerNamePrompt, - ListCliCommand listCliCommand) - : AsyncCommand + ConditionalListCliCommand conditionalListCliCommand +) : AsyncCommand { public override async Task ExecuteAsync(CommandContext context, ResetSettings settings) { @@ -22,15 +22,17 @@ public override async Task ExecuteAsync(CommandContext context, ResetSettin await ResetContainerAsync(container); - await listCliCommand.ExecuteAsync(); + await conditionalListCliCommand.ExecuteAsync(); return 0; } private async Task GetContainerAsync(IContainerIdentifierSettings settings) { - var containers = await Spinner.StartAsync("Getting running containers", - async _ => await getRunningContainersQuery.QueryAsync().ToListAsync()); + var containers = await Spinner.StartAsync( + "Getting running containers", + async _ => await getRunningContainersQuery.QueryAsync().ToListAsync() + ); if (settings.ContainerIdentifier != null) { @@ -41,7 +43,6 @@ public override async Task ExecuteAsync(CommandContext context, ResetSettin return containers.SingleOrDefault(c => c.ContainerName == identifier); } - private async Task ResetContainerAsync(Container container) { await Spinner.StartAsync( @@ -51,6 +52,7 @@ await Spinner.StartAsync( await stopAndRemoveContainerCommand.ExecuteAsync(container.Id); var id = await createContainerCommand.ExecuteAsync(container); await runContainerCommand.ExecuteAsync(id); - }); + } + ); } -} \ No newline at end of file +} diff --git a/src/Commands/Reset/ResetSettings.cs b/src/Commands/Reset/ResetSettings.cs index 251aea5..815773f 100644 --- a/src/Commands/Reset/ResetSettings.cs +++ b/src/Commands/Reset/ResetSettings.cs @@ -6,4 +6,4 @@ internal class ResetSettings : CommandSettings, IContainerIdentifierSettings { [CommandArgument(0, "[ContainerIdentifier]")] public string? ContainerIdentifier { get; set; } -} \ No newline at end of file +} diff --git a/src/Commands/Run/RunCliCommand.cs b/src/Commands/Run/RunCliCommand.cs index 79a1c27..91e4f88 100644 --- a/src/Commands/Run/RunCliCommand.cs +++ b/src/Commands/Run/RunCliCommand.cs @@ -15,8 +15,8 @@ internal class RunCliCommand( port.Config.Config config, IImageIdentifierAndTagEvaluator imageIdentifierAndTagEvaluator, IStopAndRemoveContainerCommand stopAndRemoveContainerCommand, - ListCliCommand listCliCommand) - : AsyncCommand + ConditionalListCliCommand conditionalListCliCommand +) : AsyncCommand { private const char PortSeparator = ':'; @@ -29,19 +29,23 @@ public override async Task ExecuteAsync(CommandContext context, RunSettings await TerminateOtherContainersAsync(identifier); await LaunchImageAsync(identifier, tag, settings.Reset); - await listCliCommand.ExecuteAsync(); + await conditionalListCliCommand.ExecuteAsync(); return 0; } - private async Task<(string identifier, string? tag)> GetIdentifierAndTagAsync(IImageIdentifierSettings settings) + private async Task<(string identifier, string? tag)> GetIdentifierAndTagAsync( + IImageIdentifierSettings settings + ) { if (settings.ImageIdentifier != null) { return imageIdentifierAndTagEvaluator.Evaluate(settings.ImageIdentifier); } - var identifierAndTag = await imageIdentifierPrompt.GetRunnableIdentifierAndTagFromUserAsync("run"); + var identifierAndTag = await imageIdentifierPrompt.GetRunnableIdentifierAndTagFromUserAsync( + "run" + ); return (identifierAndTag.identifier, identifierAndTag.tag); } @@ -50,35 +54,44 @@ private Task TerminateOtherContainersAsync(string identifier) var imageConfig = config.GetImageConfigByIdentifier(identifier); if (imageConfig == null) { - throw new ArgumentException($"There is no config defined for identifier '{identifier}'", - nameof(identifier)); + throw new ArgumentException( + $"There is no config defined for identifier '{identifier}'", + nameof(identifier) + ); } - var hostPorts = imageConfig.Ports - .Select(e => e.Split(PortSeparator)[0]) - .ToList(); - var spinnerTex = $"Terminating containers using host ports '{string.Join(", ", hostPorts)}'"; - return Spinner.StartAsync(spinnerTex, async _ => - { - var containers = GetRunningContainersUsingHostPortsAsync(hostPorts); - await foreach (var container in containers) - await stopContainerCommand.ExecuteAsync(container.Id); - }); + var hostPorts = imageConfig.Ports.Select(e => e.Split(PortSeparator)[0]).ToList(); + var spinnerTex = + $"Terminating containers using host ports '{string.Join(", ", hostPorts)}'"; + return Spinner.StartAsync( + spinnerTex, + async _ => + { + var containers = GetRunningContainersUsingHostPortsAsync(hostPorts); + await foreach (var container in containers) + await stopContainerCommand.ExecuteAsync(container.Id); + } + ); } private IAsyncEnumerable GetRunningContainersUsingHostPortsAsync( - IEnumerable hostPorts) + IEnumerable hostPorts + ) { - return getContainersQuery.QueryRunningAsync() + return getContainersQuery + .QueryRunningAsync() .Where(container => { // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - if (container.PortBindings is null) return false; - var usedHostPorts = container.PortBindings - .SelectMany(pb => pb.Value - .Select(hp => hp.HostPort)); - return container.PortBindings - .Any(p => { return hostPorts.Any(p => usedHostPorts.Contains(p)); }); + if (container.PortBindings is null) + return false; + var usedHostPorts = container.PortBindings.SelectMany(pb => + pb.Value.Select(hp => hp.HostPort) + ); + return container.PortBindings.Any(p => + { + return hostPorts.Any(p => usedHostPorts.Contains(p)); + }); }); } @@ -89,47 +102,69 @@ private async Task LaunchImageAsync(string identifier, string tag, bool resetCon var imageName = imageConfig.ImageName; var containerName = ContainerNameHelper.BuildContainerName(identifier, tag); - var existingImage = await Spinner.StartAsync($"Query existing image: {constructedImageName}", + var existingImage = await Spinner.StartAsync( + $"Query existing image: {constructedImageName}", async _ => { - if (imageConfig.ImageTags.Contains(tag)) return await getImageQuery.QueryAsync(imageName, tag); + if (imageConfig.ImageTags.Contains(tag)) + return await getImageQuery.QueryAsync(imageName, tag); var existingImage = await getImageQuery.QueryAsync(imageName, tag); - if (existingImage is not null) return existingImage; + if (existingImage is not null) + return existingImage; tag = $"{TagPrefixHelper.GetTagPrefix(identifier)}{tag}"; return await getImageQuery.QueryAsync(imageName, tag); - }); + } + ); if (existingImage is null) { await createImageCliChildCommand.ExecuteAsync(imageName, tag); - existingImage = await Spinner.StartAsync($"Re-query existing image: {constructedImageName}", - async _ => await getImageQuery.QueryAsync(imageName, tag)); + existingImage = await Spinner.StartAsync( + $"Re-query existing image: {constructedImageName}", + async _ => await getImageQuery.QueryAsync(imageName, tag) + ); } var tagPrefix = existingImage?.GetLabel(Constants.TagPrefix); - await Spinner.StartAsync($"Launching {constructedImageName}", async _ => - { - var containers = await getContainersQuery.QueryByContainerNameAsync(containerName).ToListAsync(); - var ports = imageConfig.Ports; - var environment = imageConfig.Environment; - if (containers.Count == 1 && resetContainer) - { - await stopAndRemoveContainerCommand.ExecuteAsync(containers.Single().Id); - var id = - await createContainerCommand.ExecuteAsync(identifier, imageName, tagPrefix, tag, ports, - environment); - await runContainerCommand.ExecuteAsync(id); - } - else if (containers.Count == 1 && !resetContainer) - { - await runContainerCommand.ExecuteAsync(containers.Single().Id); - } - else if (containers.Count == 0) + await Spinner.StartAsync( + $"Launching {constructedImageName}", + async _ => { - var id = await createContainerCommand.ExecuteAsync(identifier, imageName, tagPrefix, tag, ports, - environment); - await runContainerCommand.ExecuteAsync(id); + var containers = await getContainersQuery + .QueryByContainerNameAsync(containerName) + .ToListAsync(); + var ports = imageConfig.Ports; + var environment = imageConfig.Environment; + if (containers.Count == 1 && resetContainer) + { + await stopAndRemoveContainerCommand.ExecuteAsync(containers.Single().Id); + var id = await createContainerCommand.ExecuteAsync( + identifier, + imageName, + tagPrefix, + tag, + ports, + environment + ); + await runContainerCommand.ExecuteAsync(id); + } + else if (containers.Count == 1 && !resetContainer) + { + await runContainerCommand.ExecuteAsync(containers.Single().Id); + } + else if (containers.Count == 0) + { + var id = await createContainerCommand.ExecuteAsync( + identifier, + imageName, + tagPrefix, + tag, + ports, + environment + ); + await runContainerCommand.ExecuteAsync(id); + } } - }); + ); } -} \ No newline at end of file +} diff --git a/src/Commands/Run/RunSettings.cs b/src/Commands/Run/RunSettings.cs index 7424327..b658af4 100644 --- a/src/Commands/Run/RunSettings.cs +++ b/src/Commands/Run/RunSettings.cs @@ -6,7 +6,7 @@ public class RunSettings : CommandSettings, IImageIdentifierSettings { [CommandArgument(0, "[ImageIdentifier]")] public string? ImageIdentifier { get; set; } - + [CommandOption("-r|--reset")] public bool Reset { get; set; } -} \ No newline at end of file +} diff --git a/src/Commands/Stop/StopCliCommand.cs b/src/Commands/Stop/StopCliCommand.cs index 77e2850..e0766f4 100644 --- a/src/Commands/Stop/StopCliCommand.cs +++ b/src/Commands/Stop/StopCliCommand.cs @@ -9,16 +9,19 @@ internal class StopCliCommand : AsyncCommand private readonly IGetRunningContainersQuery _getRunningContainersQuery; private readonly IContainerNamePrompt _containerNamePrompt; private readonly IStopContainerCommand _stopContainerCommand; - private readonly ListCliCommand _listCliCommand; + private readonly ConditionalListCliCommand _conditionalListCliCommand; - public StopCliCommand(IGetRunningContainersQuery getRunningContainersQuery, - IContainerNamePrompt containerNamePrompt, IStopContainerCommand stopContainerCommand, - ListCliCommand listCliCommand) + public StopCliCommand( + IGetRunningContainersQuery getRunningContainersQuery, + IContainerNamePrompt containerNamePrompt, + IStopContainerCommand stopContainerCommand, + ConditionalListCliCommand conditionalListCliCommand + ) { _getRunningContainersQuery = getRunningContainersQuery; _containerNamePrompt = containerNamePrompt; _stopContainerCommand = stopContainerCommand; - _listCliCommand = listCliCommand; + _conditionalListCliCommand = conditionalListCliCommand; } public override async Task ExecuteAsync(CommandContext context, StopSettings settings) @@ -31,7 +34,7 @@ public override async Task ExecuteAsync(CommandContext context, StopSetting await StopContainerAsync(container); - await _listCliCommand.ExecuteAsync(); + await _conditionalListCliCommand.ExecuteAsync(); return 0; } @@ -48,11 +51,14 @@ public override async Task ExecuteAsync(CommandContext context, StopSetting return containers.SingleOrDefault(c => c.ContainerName == identifier); } - private async Task StopContainerAsync(Container container) { await Spinner.StartAsync( - $"Stopping container '{container.ContainerName}'", - async _ => { await _stopContainerCommand.ExecuteAsync(container.Id); }); + $"Stopping container '{container.ContainerName}'", + async _ => + { + await _stopContainerCommand.ExecuteAsync(container.Id); + } + ); } -} \ No newline at end of file +} diff --git a/src/Commands/Stop/StopSettings.cs b/src/Commands/Stop/StopSettings.cs index a6fa6d7..2098164 100644 --- a/src/Commands/Stop/StopSettings.cs +++ b/src/Commands/Stop/StopSettings.cs @@ -6,4 +6,4 @@ internal class StopSettings : CommandSettings, IContainerIdentifierSettings { [CommandArgument(0, "[ContainerIdentifier]")] public string? ContainerIdentifier { get; set; } -} \ No newline at end of file +} diff --git a/src/ConditionalListCliCommand.cs b/src/ConditionalListCliCommand.cs new file mode 100644 index 0000000..58fe15b --- /dev/null +++ b/src/ConditionalListCliCommand.cs @@ -0,0 +1,45 @@ +using port.Commands.List; + +namespace port; + +/// +/// Wrapper for ListCliCommand that respects command chaining detection. +/// When commands are chained together, only the last command should display the list output. +/// +internal class ConditionalListCliCommand +{ + private readonly ListCliCommand _listCliCommand; + private readonly ICommandChainDetector _commandChainDetector; + + public ConditionalListCliCommand( + ListCliCommand listCliCommand, + ICommandChainDetector commandChainDetector + ) + { + _listCliCommand = listCliCommand; + _commandChainDetector = commandChainDetector; + } + + /// + /// Executes the list command only if we should display output (i.e., not in the middle of a command chain). + /// + public async Task ExecuteAsync() + { + if (_commandChainDetector.ShouldDisplayOutput()) + { + await _listCliCommand.ExecuteAsync(); + } + } + + /// + /// Executes the list command with settings only if we should display output. + /// + public async Task ExecuteAsync(ListSettings settings) + { + if (_commandChainDetector.ShouldDisplayOutput()) + { + return await _listCliCommand.ExecuteAsync(null!, settings); + } + return 0; + } +} diff --git a/src/Config/Config.cs b/src/Config/Config.cs index 60d9dce..74248be 100644 --- a/src/Config/Config.cs +++ b/src/Config/Config.cs @@ -11,8 +11,10 @@ public ImageConfig GetImageConfigByIdentifier(string identifier) var imageConfig = ImageConfigs.SingleOrDefault(e => e.Identifier == identifier); if (imageConfig == null) { - throw new ArgumentException($"There is no config defined for identifier '{identifier}'", - nameof(identifier)); + throw new ArgumentException( + $"There is no config defined for identifier '{identifier}'", + nameof(identifier) + ); } return imageConfig; @@ -26,4 +28,4 @@ public class ImageConfig public List Ports { get; set; } = new(); public List Environment { get; set; } = new(); } -} \ No newline at end of file +} diff --git a/src/Config/Config10.cs b/src/Config/Config10.cs index 2eddc31..87197c5 100644 --- a/src/Config/Config10.cs +++ b/src/Config/Config10.cs @@ -11,8 +11,10 @@ public ImageConfig GetImageByImageName(string imageName) var imageConfig = Images.SingleOrDefault(e => e.ImageName == imageName); if (imageConfig == null) { - throw new ArgumentException($"There is no config defined for imageName '{imageName}'", - nameof(imageName)); + throw new ArgumentException( + $"There is no config defined for imageName '{imageName}'", + nameof(imageName) + ); } return imageConfig; @@ -23,13 +25,15 @@ public ImageConfig GetImageConfigByIdentifier(string identifier) var imageConfig = Images.SingleOrDefault(e => e.Identifier == identifier); if (imageConfig == null) { - throw new ArgumentException($"There is no config defined for identifier '{identifier}'", - nameof(identifier)); + throw new ArgumentException( + $"There is no config defined for identifier '{identifier}'", + nameof(identifier) + ); } return imageConfig; } - + public class ImageConfig { public string Identifier { get; set; } = null!; @@ -37,4 +41,4 @@ public class ImageConfig public string ImageTag { get; set; } = null!; public List Ports { get; set; } = new(); } -} \ No newline at end of file +} diff --git a/src/Config/ConfigFactory.cs b/src/Config/ConfigFactory.cs index 374de82..4fbb54d 100644 --- a/src/Config/ConfigFactory.cs +++ b/src/Config/ConfigFactory.cs @@ -14,7 +14,7 @@ public static Config GetOrCreateConfig() var configFilePath = GetConfigFilePath(); RenameIfNecessary(configFilePath); - + if (File.Exists(configFilePath)) { MigrateIfNecessary(configFilePath); @@ -59,7 +59,10 @@ private static void MigrateIfNecessary(string path) { case Versions.V10: var yaml = File.ReadAllText(path); - PersistConfig(ConfigMigrations.Migrate10To11(serializer.Deserialize(yaml)), path); + PersistConfig( + ConfigMigrations.Migrate10To11(serializer.Deserialize(yaml)), + path + ); break; case Versions.V11: break; @@ -75,32 +78,24 @@ private static Config LoadConfig(string path) return serializer.Deserialize(yaml); } - private static Config CreateDefault() => new Config - { - DockerEndpoint = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) - ? "npipe://./pipe/docker_engine" - : "unix:///var/run/docker.sock", - ImageConfigs = new List + private static Config CreateDefault() => + new Config { - new() + DockerEndpoint = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? "npipe://./pipe/docker_engine" + : "unix:///var/run/docker.sock", + ImageConfigs = new List { - Identifier = "Getting.Started", - ImageName = "docker/getting-started", - ImageTags = new List - { - "latest" - }, - Ports = new List + new() { - "80:80" + Identifier = "Getting.Started", + ImageName = "docker/getting-started", + ImageTags = new List { "latest" }, + Ports = new List { "80:80" }, + Environment = new List { "80:80" }, }, - Environment = new List - { - "80:80" - } - } - } - }; + }, + }; private static void PersistConfig(Config config, string path) { @@ -110,9 +105,9 @@ private static void PersistConfig(Config config, string path) var yaml = serializer.Serialize(config); File.WriteAllText(path, yaml); } - + public class ConfigVersion { public string Version { get; set; } = null!; } -} \ No newline at end of file +} diff --git a/src/Config/ConfigMigrations.cs b/src/Config/ConfigMigrations.cs index aff0e01..0c27f1b 100644 --- a/src/Config/ConfigMigrations.cs +++ b/src/Config/ConfigMigrations.cs @@ -7,16 +7,15 @@ public static Config Migrate10To11(Config10 config) return new Config { DockerEndpoint = config.DockerEndpoint, - ImageConfigs = config.Images.Select(e => new Config.ImageConfig - { - Identifier = e.Identifier, - ImageName = e.ImageName, - Ports = e.Ports, - ImageTags = new List + ImageConfigs = config + .Images.Select(e => new Config.ImageConfig { - e.ImageTag - } - }).ToList() + Identifier = e.Identifier, + ImageName = e.ImageName, + Ports = e.Ports, + ImageTags = new List { e.ImageTag }, + }) + .ToList(), }; } -} \ No newline at end of file +} diff --git a/src/Config/ImageConfig.cs b/src/Config/ImageConfig.cs index 4a25dcf..9c75e6b 100644 --- a/src/Config/ImageConfig.cs +++ b/src/Config/ImageConfig.cs @@ -1,2 +1 @@ namespace port.Config; - diff --git a/src/Config/Versions.cs b/src/Config/Versions.cs index c0af2b8..731b7fa 100644 --- a/src/Config/Versions.cs +++ b/src/Config/Versions.cs @@ -4,4 +4,4 @@ public static class Versions { public const string V10 = "1.0"; public const string V11 = "1.1"; -} \ No newline at end of file +} diff --git a/src/Constants.cs b/src/Constants.cs index 8433b75..e88aea7 100644 --- a/src/Constants.cs +++ b/src/Constants.cs @@ -4,5 +4,5 @@ public static class Constants { public const string BaseTagLabel = "com.port.base.tag"; public const string IdentifierLabel = "com.port.identifier"; - public const string TagPrefix ="com.port.tag.prefix"; -} \ No newline at end of file + public const string TagPrefix = "com.port.tag.prefix"; +} diff --git a/src/ContainerNameHelper.cs b/src/ContainerNameHelper.cs index a4d28c1..372d4f9 100644 --- a/src/ContainerNameHelper.cs +++ b/src/ContainerNameHelper.cs @@ -4,7 +4,10 @@ public static class ContainerNameHelper { private const string Separator = "."; - public static bool TryGetContainerNameAndTag(string containerName, out (string containerName, string tag) nameAndTag) + public static bool TryGetContainerNameAndTag( + string containerName, + out (string containerName, string tag) nameAndTag + ) { nameAndTag = (containerName, string.Empty); var idx = containerName.LastIndexOf(Separator, StringComparison.Ordinal); @@ -16,6 +19,7 @@ public static bool TryGetContainerNameAndTag(string containerName, out (string c nameAndTag = (containerName[..idx], containerName[(idx + 1)..]); return true; } - - public static string BuildContainerName(string identifier, string? tag) => $"{identifier}{Separator}{tag}"; -} \ No newline at end of file + + public static string BuildContainerName(string identifier, string? tag) => + $"{identifier}{Separator}{tag}"; +} diff --git a/src/ContainerNamePrompt.cs b/src/ContainerNamePrompt.cs index 5b5b5a5..80ac6ea 100644 --- a/src/ContainerNamePrompt.cs +++ b/src/ContainerNamePrompt.cs @@ -4,8 +4,10 @@ namespace port; internal class ContainerNamePrompt : IContainerNamePrompt { - public string GetIdentifierOfContainerFromUser(IReadOnlyCollection containers, - string command) + public string GetIdentifierOfContainerFromUser( + IReadOnlyCollection containers, + string command + ) { switch (containers.Count) { @@ -32,13 +34,12 @@ private static SelectionPrompt CreateSelectionPrompt(string command) { return o switch { - Container container => - $"[white]{container.ContainerName}[/]", - _ => o as string ?? throw new InvalidOperationException() + Container container => $"[white]{container.ContainerName}[/]", + _ => o as string ?? throw new InvalidOperationException(), }; }) .PageSize(10) .Title($"Select container you wish to [green]{command}[/]") .MoreChoicesText("[grey](Move up and down to reveal more containers)[/]"); } -} \ No newline at end of file +} diff --git a/src/CreateContainerCommand.cs b/src/CreateContainerCommand.cs index 2c92fb3..b627cb1 100644 --- a/src/CreateContainerCommand.cs +++ b/src/CreateContainerCommand.cs @@ -15,9 +15,14 @@ public CreateContainerCommand(IDockerClient dockerClient, IGetImageQuery getImag _getImageQuery = getImageQuery; } - public async Task ExecuteAsync(string containerIdentifier, string imageIdentifier, string? tagPrefix, + public async Task ExecuteAsync( + string containerIdentifier, + string imageIdentifier, + string? tagPrefix, string? tag, - IEnumerable ports, IList environment) + IEnumerable ports, + IList environment + ) { var portBindings = ports .Select(e => e.Split(PortSeparator)) @@ -27,22 +32,23 @@ public async Task ExecuteAsync(string containerIdentifier, string imageI var baseTag = image?.GetLabel(Constants.BaseTagLabel) ?? image?.BaseImage?.Tag ?? tag; var labels = new Dictionary { - { Constants.IdentifierLabel, containerIdentifier } + { Constants.IdentifierLabel, containerIdentifier }, }; - if (baseTag is not null) labels.Add(Constants.BaseTagLabel, baseTag); - if (tagPrefix is not null) labels.Add(Constants.TagPrefix, tagPrefix); - await _dockerClient.Containers.CreateContainerAsync(new CreateContainerParameters - { - Name = containerName, - Image = ImageNameHelper.BuildImageName(imageIdentifier, tag), - Env = environment, - HostConfig = new HostConfig + if (baseTag is not null) + labels.Add(Constants.BaseTagLabel, baseTag); + if (tagPrefix is not null) + labels.Add(Constants.TagPrefix, tagPrefix); + await _dockerClient.Containers.CreateContainerAsync( + new CreateContainerParameters { - PortBindings = portBindings - }, - ExposedPorts = portBindings.Keys.ToDictionary(port => port, _ => new EmptyStruct()), - Labels = labels - }); + Name = containerName, + Image = ImageNameHelper.BuildImageName(imageIdentifier, tag), + Env = environment, + HostConfig = new HostConfig { PortBindings = portBindings }, + ExposedPorts = portBindings.Keys.ToDictionary(port => port, _ => new EmptyStruct()), + Labels = labels, + } + ); return containerName; } @@ -50,24 +56,28 @@ public async Task ExecuteAsync(Container container, string tagPrefix, st { var portBindings = container.PortBindings; var environment = container.Environment; - var containerName = ContainerNameHelper.BuildContainerName(container.ContainerIdentifier, newTag.StartsWith(tagPrefix) ? newTag[tagPrefix.Length..] : newTag); + var containerName = ContainerNameHelper.BuildContainerName( + container.ContainerIdentifier, + newTag.StartsWith(tagPrefix) ? newTag[tagPrefix.Length..] : newTag + ); var labels = new Dictionary(); var identifier = container.GetLabel(Constants.IdentifierLabel); - if (identifier is not null) labels.Add(Constants.IdentifierLabel, identifier); + if (identifier is not null) + labels.Add(Constants.IdentifierLabel, identifier); var baseTag = container.GetLabel(Constants.BaseTagLabel); - if (baseTag is not null) labels.Add(Constants.BaseTagLabel, baseTag); - var createContainerResponse = await _dockerClient.Containers.CreateContainerAsync(new CreateContainerParameters - { - Name = containerName, - Image = ImageNameHelper.BuildImageName(container.ImageIdentifier, newTag), - HostConfig = new HostConfig + if (baseTag is not null) + labels.Add(Constants.BaseTagLabel, baseTag); + var createContainerResponse = await _dockerClient.Containers.CreateContainerAsync( + new CreateContainerParameters { - PortBindings = portBindings - }, - Env = environment, - ExposedPorts = portBindings.Keys.ToDictionary(port => port, _ => new EmptyStruct()), - Labels = labels - }); + Name = containerName, + Image = ImageNameHelper.BuildImageName(container.ImageIdentifier, newTag), + HostConfig = new HostConfig { PortBindings = portBindings }, + Env = environment, + ExposedPorts = portBindings.Keys.ToDictionary(port => port, _ => new EmptyStruct()), + Labels = labels, + } + ); return createContainerResponse.ID; } @@ -77,32 +87,30 @@ public async Task ExecuteAsync(Container container) var environment = container.Environment; var labels = new Dictionary(); var identifier = container.GetLabel(Constants.IdentifierLabel); - if (identifier is not null) labels.Add(Constants.IdentifierLabel, identifier); + if (identifier is not null) + labels.Add(Constants.IdentifierLabel, identifier); var baseTag = container.GetLabel(Constants.BaseTagLabel); - if (baseTag is not null) labels.Add(Constants.BaseTagLabel, baseTag); - var createContainerResponse = await _dockerClient.Containers.CreateContainerAsync(new CreateContainerParameters - { - Name = container.ContainerName, - Image = ImageNameHelper.BuildImageName(container.ImageIdentifier, container.ImageTag), - HostConfig = new HostConfig + if (baseTag is not null) + labels.Add(Constants.BaseTagLabel, baseTag); + var createContainerResponse = await _dockerClient.Containers.CreateContainerAsync( + new CreateContainerParameters { - PortBindings = portBindings - }, - Env = environment, - ExposedPorts = portBindings.Keys.ToDictionary(port => port, _ => new EmptyStruct()), - Labels = labels - }); + Name = container.ContainerName, + Image = ImageNameHelper.BuildImageName( + container.ImageIdentifier, + container.ImageTag + ), + HostConfig = new HostConfig { PortBindings = portBindings }, + Env = environment, + ExposedPorts = portBindings.Keys.ToDictionary(port => port, _ => new EmptyStruct()), + Labels = labels, + } + ); return createContainerResponse.ID; } private static IList CreateHostPortList(string hostPort) { - return new List - { - new() - { - HostPort = hostPort - } - }; + return new List { new() { HostPort = hostPort } }; } -} \ No newline at end of file +} diff --git a/src/CreateImageCliDependentCommand.cs b/src/CreateImageCliDependentCommand.cs index 64cf3b6..4054d43 100644 --- a/src/CreateImageCliDependentCommand.cs +++ b/src/CreateImageCliDependentCommand.cs @@ -15,26 +15,30 @@ public async Task ExecuteAsync(string imageName, string? tag) { var tasks = new Dictionary(); var lockObject = new object(); - await AnsiConsole.Progress() + await AnsiConsole + .Progress() .Columns( new PercentageColumn(), new ProgressBarColumn(), new SpinnerColumn(), - new TaskDescriptionColumn - { - Alignment = Justify.Left - }) + new TaskDescriptionColumn { Alignment = Justify.Left } + ) .StartAsync(async ctx => { - _createImageCommand.ProgressObservable - .Subscribe(progress => UpdateProgressTasks(tag, lockObject, progress, tasks, ctx)); + _createImageCommand.ProgressObservable.Subscribe(progress => + UpdateProgressTasks(tag, lockObject, progress, tasks, ctx) + ); await _createImageCommand.ExecuteAsync(imageName, tag); }); } - private static void UpdateProgressTasks(string? tag, object lockObject, Progress progress, + private static void UpdateProgressTasks( + string? tag, + object lockObject, + Progress progress, IDictionary tasks, - ProgressContext ctx) + ProgressContext ctx + ) { lock (lockObject) { @@ -64,4 +68,4 @@ private static void UpdateProgressTasks(string? tag, object lockObject, Progress } } } -} \ No newline at end of file +} diff --git a/src/CreateImageCommand.cs b/src/CreateImageCommand.cs index 4c7a27d..89afd12 100644 --- a/src/CreateImageCommand.cs +++ b/src/CreateImageCommand.cs @@ -25,11 +25,9 @@ public async Task ExecuteAsync(string imageName, string? tag) _progressSubscriber.Subscribe(progress, _progressSubject); await _dockerClient.Images.CreateImageAsync( - new ImagesCreateParameters - { - FromImage = imageName, - Tag = tag - }, - null, progress); + new ImagesCreateParameters { FromImage = imageName, Tag = tag }, + null, + progress + ); } -} \ No newline at end of file +} diff --git a/src/DigestHelper.cs b/src/DigestHelper.cs index 9b8027d..592f405 100644 --- a/src/DigestHelper.cs +++ b/src/DigestHelper.cs @@ -4,7 +4,10 @@ public static class DigestHelper { private const string Separator = "@"; - public static bool TryGetImageNameAndId(string digest, out (string imageName, string tag) nameAndId) + public static bool TryGetImageNameAndId( + string digest, + out (string imageName, string tag) nameAndId + ) { nameAndId = (digest, string.Empty); var idx = digest.LastIndexOf(Separator, StringComparison.Ordinal); @@ -16,4 +19,4 @@ public static bool TryGetImageNameAndId(string digest, out (string imageName, st nameAndId = (digest[..idx], digest[(idx + 1)..]); return true; } -} \ No newline at end of file +} diff --git a/src/DoesImageExistQuery.cs b/src/DoesImageExistQuery.cs index 1805101..1eb05a0 100644 --- a/src/DoesImageExistQuery.cs +++ b/src/DoesImageExistQuery.cs @@ -16,16 +16,16 @@ public async Task QueryAsync(string imageName, string? tag) { var parameters = new ImagesListParameters { - Filters = new Dictionary>() + Filters = new Dictionary>(), }; - parameters.Filters.Add("reference", new Dictionary - { - { imageName, true } - }); + parameters.Filters.Add("reference", new Dictionary { { imageName, true } }); var imagesListResponses = await _dockerClient.Images.ListImagesAsync(parameters); - return imagesListResponses - .Any(e => - tag == null && !e.RepoTags.Any() - || e.RepoTags != null && e.RepoTags.Any(repoTag => repoTag.Contains(ImageNameHelper.BuildImageName(imageName, tag)))); + return imagesListResponses.Any(e => + tag == null && !e.RepoTags.Any() + || e.RepoTags != null + && e.RepoTags.Any(repoTag => + repoTag.Contains(ImageNameHelper.BuildImageName(imageName, tag)) + ) + ); } -} \ No newline at end of file +} diff --git a/src/GetContainersQuery.cs b/src/GetContainersQuery.cs index 4961d44..d8e4b0a 100644 --- a/src/GetContainersQuery.cs +++ b/src/GetContainersQuery.cs @@ -14,19 +14,22 @@ public GetContainersQuery(IDockerClient dockerClient) _dockerClient = dockerClient; _containerListResponses = new AsyncLazy>(token => _dockerClient.Containers.ListContainersAsync( - new ContainersListParameters - { - Limit = long.MaxValue - }, token)); + new ContainersListParameters { Limit = long.MaxValue }, + token + ) + ); } public async IAsyncEnumerable QueryRunningAsync() { - var containerListResponses = await _containerListResponses.WithCancellation(CancellationToken.None); + var containerListResponses = await _containerListResponses.WithCancellation( + CancellationToken.None + ); foreach (var containerListResponse in containerListResponses) { - var inspectContainerResponse = - await _dockerClient.Containers.InspectContainerAsync(containerListResponse.ID); + var inspectContainerResponse = await _dockerClient.Containers.InspectContainerAsync( + containerListResponse.ID + ); var container = new Container(containerListResponse, inspectContainerResponse); if (container.Running) { @@ -35,16 +38,24 @@ public async IAsyncEnumerable QueryRunningAsync() } } - public async IAsyncEnumerable QueryByContainerIdentifierAndTagAsync(string containerIdentifier, - string? tag) + public async IAsyncEnumerable QueryByContainerIdentifierAndTagAsync( + string containerIdentifier, + string? tag + ) { - var containerListResponses = await _containerListResponses.WithCancellation(CancellationToken.None); + var containerListResponses = await _containerListResponses.WithCancellation( + CancellationToken.None + ); foreach (var containerListResponse in containerListResponses) { - var inspectContainerResponse = - await _dockerClient.Containers.InspectContainerAsync(containerListResponse.ID); + var inspectContainerResponse = await _dockerClient.Containers.InspectContainerAsync( + containerListResponse.ID + ); var container = new Container(containerListResponse, inspectContainerResponse); - if (containerIdentifier == container.ContainerIdentifier && tag == container.ContainerTag) + if ( + containerIdentifier == container.ContainerIdentifier + && tag == container.ContainerTag + ) { yield return container; } @@ -53,12 +64,16 @@ public async IAsyncEnumerable QueryByContainerIdentifierAndTagAsync(s public async IAsyncEnumerable QueryByImageIdAsync(string imageId) { - var containerListResponses = await _containerListResponses.WithCancellation(CancellationToken.None); + var containerListResponses = await _containerListResponses.WithCancellation( + CancellationToken.None + ); foreach (var containerListResponse in containerListResponses) { - if (containerListResponse.ImageID != imageId) continue; - var inspectContainerResponse = - await _dockerClient.Containers.InspectContainerAsync(containerListResponse.ID); + if (containerListResponse.ImageID != imageId) + continue; + var inspectContainerResponse = await _dockerClient.Containers.InspectContainerAsync( + containerListResponse.ID + ); var container = new Container(containerListResponse, inspectContainerResponse); yield return container; } @@ -66,11 +81,14 @@ public async IAsyncEnumerable QueryByImageIdAsync(string imageId) public async IAsyncEnumerable QueryByContainerNameAsync(string containerName) { - var containerListResponses = await _containerListResponses.WithCancellation(CancellationToken.None); + var containerListResponses = await _containerListResponses.WithCancellation( + CancellationToken.None + ); foreach (var containerListResponse in containerListResponses) { - var inspectContainerResponse = - await _dockerClient.Containers.InspectContainerAsync(containerListResponse.ID); + var inspectContainerResponse = await _dockerClient.Containers.InspectContainerAsync( + containerListResponse.ID + ); var container = new Container(containerListResponse, inspectContainerResponse); if (containerName == container.ContainerName) { @@ -78,4 +96,4 @@ public async IAsyncEnumerable QueryByContainerNameAsync(string contai } } } -} \ No newline at end of file +} diff --git a/src/GetImageIdQuery.cs b/src/GetImageIdQuery.cs index 478a462..b58750f 100644 --- a/src/GetImageIdQuery.cs +++ b/src/GetImageIdQuery.cs @@ -16,17 +16,18 @@ public async Task> QueryAsync(string imageName, string? tag) { var parameters = new ImagesListParameters { - Filters = new Dictionary>() + Filters = new Dictionary>(), }; - parameters.Filters.Add("reference", new Dictionary - { - { imageName, true } - }); + parameters.Filters.Add("reference", new Dictionary { { imageName, true } }); var imagesListResponses = await _dockerClient.Images.ListImagesAsync(parameters); return imagesListResponses .Where(e => tag == null && !e.RepoTags.Any() - || e.RepoTags != null && e.RepoTags.Any(repoTag => repoTag.Contains(ImageNameHelper.BuildImageName(imageName, tag)))) + || e.RepoTags != null + && e.RepoTags.Any(repoTag => + repoTag.Contains(ImageNameHelper.BuildImageName(imageName, tag)) + ) + ) .Select(e => e.ID); } -} \ No newline at end of file +} diff --git a/src/GetImageQuery.cs b/src/GetImageQuery.cs index bf1370b..2969499 100644 --- a/src/GetImageQuery.cs +++ b/src/GetImageQuery.cs @@ -18,33 +18,48 @@ public GetImageQuery(IDockerClient dockerClient, IGetContainersQuery getContaine { var parameters = new ImagesListParameters { - Filters = new Dictionary>() + Filters = new Dictionary>(), }; - parameters.Filters.Add("reference", new Dictionary - { - { imageName, true } - }); + parameters.Filters.Add("reference", new Dictionary { { imageName, true } }); var imagesListResponses = await _dockerClient.Images.ListImagesAsync(parameters); - var imagesListResponse = imagesListResponses - .SingleOrDefault(e => - tag == null && !e.RepoTags.Any() - || e.RepoTags != null && e.RepoTags.Any(repoTag => - repoTag.Contains(ImageNameHelper.BuildImageName(imageName, tag)))); + var imagesListResponse = imagesListResponses.SingleOrDefault(e => + tag == null && !e.RepoTags.Any() + || e.RepoTags != null + && e.RepoTags.Any(repoTag => + repoTag.Contains(ImageNameHelper.BuildImageName(imageName, tag)) + ) + ); if (imagesListResponse == null) { return null; } - var containers = await _getContainersQuery.QueryByImageIdAsync(imagesListResponse.ID).ToListAsync(); - var imageInspectResponse = await _dockerClient.Images.InspectImageAsync(imagesListResponse.ID); - return await ConvertToImage(imageInspectResponse.Config.Labels, imageName, tag, imagesListResponse, containers); + var containers = await _getContainersQuery + .QueryByImageIdAsync(imagesListResponse.ID) + .ToListAsync(); + var imageInspectResponse = await _dockerClient.Images.InspectImageAsync( + imagesListResponse.ID + ); + return await ConvertToImage( + imageInspectResponse.Config.Labels, + imageName, + tag, + imagesListResponse, + containers + ); } - private async Task ConvertToImage(IDictionary? labels, string imageName, string? tag, + private async Task ConvertToImage( + IDictionary? labels, + string imageName, + string? tag, ImagesListResponse imagesListResponse, - IReadOnlyCollection containers) + IReadOnlyCollection containers + ) { - var imageInspectResult = await _dockerClient.Images.InspectImageAsync(imagesListResponse.ID); + var imageInspectResult = await _dockerClient.Images.InspectImageAsync( + imagesListResponse.ID + ); return new Image(labels ?? new Dictionary()) { Name = imageName, @@ -52,18 +67,24 @@ public GetImageQuery(IDockerClient dockerClient, IGetContainersQuery getContaine IsSnapshot = false, Existing = true, Created = imagesListResponse.Created, - Containers = containers.Where(c => imageName == c.ImageIdentifier && tag == c.ImageTag).ToList(), + Containers = containers + .Where(c => imageName == c.ImageIdentifier && tag == c.ImageTag) + .ToList(), Id = imagesListResponse.ID, - ParentId = string.IsNullOrEmpty(imageInspectResult.Parent) ? null : imageInspectResult.Parent, + ParentId = string.IsNullOrEmpty(imageInspectResult.Parent) + ? null + : imageInspectResult.Parent, Parent = string.IsNullOrEmpty(imageInspectResult.Parent) ? null - : await QueryParent(imageInspectResult.Parent, containers) + : await QueryParent(imageInspectResult.Parent, containers), }; } private async Task QueryParent(string id, IReadOnlyCollection containers) { - var imagesListResponses = await _dockerClient.Images.ListImagesAsync(new ImagesListParameters()); + var imagesListResponses = await _dockerClient.Images.ListImagesAsync( + new ImagesListParameters() + ); var imagesListResponse = imagesListResponses.SingleOrDefault(e => e.ID == id); @@ -72,16 +93,25 @@ public GetImageQuery(IDockerClient dockerClient, IGetContainersQuery getContaine return null; } - var imageInspectResponse = await _dockerClient.Images.InspectImageAsync(imagesListResponse.ID); + var imageInspectResponse = await _dockerClient.Images.InspectImageAsync( + imagesListResponse.ID + ); var labels = imageInspectResponse.Config.Labels; if (imagesListResponse.RepoTags != null) { if (imagesListResponse.RepoTags.Count == 1) { - var (imageName1, tag) = ImageNameHelper.GetImageNameAndTag(imagesListResponse.RepoTags.Single()); - return await ConvertToImage(labels, imageName1, tag, imagesListResponse, - containers); + var (imageName1, tag) = ImageNameHelper.GetImageNameAndTag( + imagesListResponse.RepoTags.Single() + ); + return await ConvertToImage( + labels, + imageName1, + tag, + imagesListResponse, + containers + ); } var baseTag = labels.SingleOrDefault(l => l.Key == Constants.BaseTagLabel).Value; @@ -89,16 +119,26 @@ public GetImageQuery(IDockerClient dockerClient, IGetContainersQuery getContaine { var (imageName1, tag) = ImageNameHelper.GetImageNameAndTag(repoTag); if (tag == baseTag) - return await ConvertToImage(labels, imageName1, tag, imagesListResponse, - containers); + return await ConvertToImage( + labels, + imageName1, + tag, + imagesListResponse, + containers + ); } } var digest = imagesListResponse.RepoDigests?.SingleOrDefault(); if (digest != null && DigestHelper.TryGetImageNameAndId(digest, out var nameNameAndId)) - return await ConvertToImage(labels, nameNameAndId.imageName, null, imagesListResponse, - containers); + return await ConvertToImage( + labels, + nameNameAndId.imageName, + null, + imagesListResponse, + containers + ); return null; } -} \ No newline at end of file +} diff --git a/src/GetRunningContainersQuery.cs b/src/GetRunningContainersQuery.cs index b599b5f..e94cd9c 100644 --- a/src/GetRunningContainersQuery.cs +++ b/src/GetRunningContainersQuery.cs @@ -17,24 +17,30 @@ public GetRunningContainersQuery(IDockerClient dockerClient, port.Config.Config public async IAsyncEnumerable QueryAsync() { var images = _config.ImageConfigs; - var containerNames = images.SelectMany(image - => image.ImageTags.Select(tag => ContainerNameHelper.BuildContainerName(image.Identifier, tag))) + var containerNames = images + .SelectMany(image => + image.ImageTags.Select(tag => + ContainerNameHelper.BuildContainerName(image.Identifier, tag) + ) + ) .ToList(); var containerListResponses = await _dockerClient.Containers.ListContainersAsync( - new ContainersListParameters - { - Limit = long.MaxValue - }); + new ContainersListParameters { Limit = long.MaxValue } + ); foreach (var containerListResponse in containerListResponses) { - var inspectContainerResponse = - await _dockerClient.Containers.InspectContainerAsync(containerListResponse.ID); + var inspectContainerResponse = await _dockerClient.Containers.InspectContainerAsync( + containerListResponse.ID + ); var container = new Container(containerListResponse, inspectContainerResponse); - if (container.Running && containerNames.Any(cn => container.ContainerName.StartsWith(cn))) + if ( + container.Running + && containerNames.Any(cn => container.ContainerName.StartsWith(cn)) + ) { yield return container; } } } -} \ No newline at end of file +} diff --git a/src/IAllImagesQuery.cs b/src/IAllImagesQuery.cs index 195a739..b58e16f 100644 --- a/src/IAllImagesQuery.cs +++ b/src/IAllImagesQuery.cs @@ -7,4 +7,4 @@ internal interface IAllImagesQuery IAsyncEnumerable QueryAsync(); IAsyncEnumerable<(string Id, string ParentId)> QueryAllImagesWithParentAsync(); Task> QueryByImageConfigAsync(port.Config.Config.ImageConfig imageConfig); -} \ No newline at end of file +} diff --git a/src/ICommandChainDetector.cs b/src/ICommandChainDetector.cs new file mode 100644 index 0000000..488a610 --- /dev/null +++ b/src/ICommandChainDetector.cs @@ -0,0 +1,14 @@ +namespace port; + +/// +/// Service to detect if the current command is being executed as part of a command chain. +/// This helps determine when to suppress intermediate output like listing current state. +/// +public interface ICommandChainDetector +{ + /// + /// Determines if the current process is likely the last command in a command chain. + /// Returns true if this command should display output, false if it should be suppressed. + /// + bool ShouldDisplayOutput(); +} diff --git a/src/IContainerIdentifierSettings.cs b/src/IContainerIdentifierSettings.cs index 6f55224..855a035 100644 --- a/src/IContainerIdentifierSettings.cs +++ b/src/IContainerIdentifierSettings.cs @@ -3,4 +3,4 @@ namespace port; public interface IContainerIdentifierSettings { public string? ContainerIdentifier { get; set; } -} \ No newline at end of file +} diff --git a/src/IContainerNamePrompt.cs b/src/IContainerNamePrompt.cs index bdb1f07..eefd395 100644 --- a/src/IContainerNamePrompt.cs +++ b/src/IContainerNamePrompt.cs @@ -2,6 +2,8 @@ namespace port; public interface IContainerNamePrompt { - string GetIdentifierOfContainerFromUser(IReadOnlyCollection readOnlyCollection, - string command); -} \ No newline at end of file + string GetIdentifierOfContainerFromUser( + IReadOnlyCollection readOnlyCollection, + string command + ); +} diff --git a/src/ICreateContainerCommand.cs b/src/ICreateContainerCommand.cs index f89bb97..028b039 100644 --- a/src/ICreateContainerCommand.cs +++ b/src/ICreateContainerCommand.cs @@ -2,8 +2,14 @@ namespace port; public interface ICreateContainerCommand { - Task ExecuteAsync(string containerIdentifier, string imageIdentifier, string? tagPrefix, string? tag, - IEnumerable ports, IList environment); + Task ExecuteAsync( + string containerIdentifier, + string imageIdentifier, + string? tagPrefix, + string? tag, + IEnumerable ports, + IList environment + ); Task ExecuteAsync(Container container, string tagPrefix, string newTag); Task ExecuteAsync(Container container); -} \ No newline at end of file +} diff --git a/src/ICreateImageCliDependentCommand.cs b/src/ICreateImageCliDependentCommand.cs index 16d5e7a..f98f1f3 100644 --- a/src/ICreateImageCliDependentCommand.cs +++ b/src/ICreateImageCliDependentCommand.cs @@ -3,4 +3,4 @@ namespace port; public interface ICreateImageCliChildCommand { Task ExecuteAsync(string imageName, string? tag); -} \ No newline at end of file +} diff --git a/src/ICreateImageCommand.cs b/src/ICreateImageCommand.cs index 8aa6a67..58024da 100644 --- a/src/ICreateImageCommand.cs +++ b/src/ICreateImageCommand.cs @@ -5,4 +5,4 @@ public interface ICreateImageCommand Task ExecuteAsync(string imageName, string? tag); IObservable ProgressObservable { get; } -} \ No newline at end of file +} diff --git a/src/IDoesImageExistQuery.cs b/src/IDoesImageExistQuery.cs index 7263b3a..7c47875 100644 --- a/src/IDoesImageExistQuery.cs +++ b/src/IDoesImageExistQuery.cs @@ -3,4 +3,4 @@ namespace port; internal interface IDoesImageExistQuery { Task QueryAsync(string imageName, string? tag); -} \ No newline at end of file +} diff --git a/src/IGetContainersQuery.cs b/src/IGetContainersQuery.cs index 83b9129..cc2186b 100644 --- a/src/IGetContainersQuery.cs +++ b/src/IGetContainersQuery.cs @@ -3,7 +3,10 @@ namespace port; public interface IGetContainersQuery { IAsyncEnumerable QueryRunningAsync(); - IAsyncEnumerable QueryByContainerIdentifierAndTagAsync(string containerIdentifier, string? tag); + IAsyncEnumerable QueryByContainerIdentifierAndTagAsync( + string containerIdentifier, + string? tag + ); IAsyncEnumerable QueryByImageIdAsync(string imageId); IAsyncEnumerable QueryByContainerNameAsync(string containerName); -} \ No newline at end of file +} diff --git a/src/IGetImageIdQuery.cs b/src/IGetImageIdQuery.cs index a122fd6..9a28b31 100644 --- a/src/IGetImageIdQuery.cs +++ b/src/IGetImageIdQuery.cs @@ -3,4 +3,4 @@ namespace port; internal interface IGetImageIdQuery { Task> QueryAsync(string imageName, string? tag); -} \ No newline at end of file +} diff --git a/src/IGetImageQuery.cs b/src/IGetImageQuery.cs index 1ace920..3b11261 100644 --- a/src/IGetImageQuery.cs +++ b/src/IGetImageQuery.cs @@ -3,4 +3,4 @@ namespace port; public interface IGetImageQuery { Task QueryAsync(string imageName, string? tag); -} \ No newline at end of file +} diff --git a/src/IGetRunningContainersQuery.cs b/src/IGetRunningContainersQuery.cs index 37b80c3..313c556 100644 --- a/src/IGetRunningContainersQuery.cs +++ b/src/IGetRunningContainersQuery.cs @@ -3,4 +3,4 @@ namespace port; internal interface IGetRunningContainersQuery { IAsyncEnumerable QueryAsync(); -} \ No newline at end of file +} diff --git a/src/IImageIdentifierAndTagEvaluator.cs b/src/IImageIdentifierAndTagEvaluator.cs index 6168ced..e4130b0 100644 --- a/src/IImageIdentifierAndTagEvaluator.cs +++ b/src/IImageIdentifierAndTagEvaluator.cs @@ -3,4 +3,4 @@ namespace port; public interface IImageIdentifierAndTagEvaluator { (string identifier, string tag) Evaluate(string imageIdentifier); -} \ No newline at end of file +} diff --git a/src/IImageIdentifierPrompt.cs b/src/IImageIdentifierPrompt.cs index 684c33a..2b15a9c 100644 --- a/src/IImageIdentifierPrompt.cs +++ b/src/IImageIdentifierPrompt.cs @@ -4,7 +4,9 @@ public interface IImageIdentifierPrompt { string GetBaseIdentifierFromUser(string command); Task<(string identifier, string? tag)> GetBaseIdentifierAndTagFromUserAsync(string command); - Task<(string identifier, string? tag)> GetDownloadedIdentifierAndTagFromUserAsync(string command); + Task<(string identifier, string? tag)> GetDownloadedIdentifierAndTagFromUserAsync( + string command + ); Task<(string identifier, string? tag)> GetRunnableIdentifierAndTagFromUserAsync(string command); -} \ No newline at end of file +} diff --git a/src/IImageIdentifierSettings.cs b/src/IImageIdentifierSettings.cs index a43528c..05fabdc 100644 --- a/src/IImageIdentifierSettings.cs +++ b/src/IImageIdentifierSettings.cs @@ -3,4 +3,4 @@ namespace port; public interface IImageIdentifierSettings { public string? ImageIdentifier { get; set; } -} \ No newline at end of file +} diff --git a/src/IProgressSubscriber.cs b/src/IProgressSubscriber.cs index b0693b5..955bf99 100644 --- a/src/IProgressSubscriber.cs +++ b/src/IProgressSubscriber.cs @@ -5,4 +5,4 @@ namespace port; internal interface IProgressSubscriber { IDisposable Subscribe(Progress progress, IObserver observer); -} \ No newline at end of file +} diff --git a/src/IRemoveImageCommand.cs b/src/IRemoveImageCommand.cs index 54fddc1..16eba5f 100644 --- a/src/IRemoveImageCommand.cs +++ b/src/IRemoveImageCommand.cs @@ -4,4 +4,4 @@ internal interface IRemoveImageCommand { Task ExecuteAsync(string imageName, string? tag); Task ExecuteAsync(string id); -} \ No newline at end of file +} diff --git a/src/IRemoveImagesCliDependentCommand.cs b/src/IRemoveImagesCliDependentCommand.cs index 8801d69..5faadbd 100644 --- a/src/IRemoveImagesCliDependentCommand.cs +++ b/src/IRemoveImagesCliDependentCommand.cs @@ -5,4 +5,4 @@ namespace port; internal interface IRemoveImagesCliDependentCommand { Task> ExecuteAsync(List imageIds, StatusContext ctx); -} \ No newline at end of file +} diff --git a/src/IRunContainerCommand.cs b/src/IRunContainerCommand.cs index d04a4d2..9812c19 100644 --- a/src/IRunContainerCommand.cs +++ b/src/IRunContainerCommand.cs @@ -4,4 +4,4 @@ public interface IRunContainerCommand { Task ExecuteAsync(string id); Task ExecuteAsync(Container container); -} \ No newline at end of file +} diff --git a/src/IStopAndRemoveContainerCommand.cs b/src/IStopAndRemoveContainerCommand.cs index b3bac7d..425ef97 100644 --- a/src/IStopAndRemoveContainerCommand.cs +++ b/src/IStopAndRemoveContainerCommand.cs @@ -3,4 +3,4 @@ namespace port; public interface IStopAndRemoveContainerCommand { Task ExecuteAsync(string containerId); -} \ No newline at end of file +} diff --git a/src/IStopContainerCommand.cs b/src/IStopContainerCommand.cs index c7c4e2c..850d762 100644 --- a/src/IStopContainerCommand.cs +++ b/src/IStopContainerCommand.cs @@ -3,4 +3,4 @@ namespace port; public interface IStopContainerCommand { public Task ExecuteAsync(string containerId); -} \ No newline at end of file +} diff --git a/src/Image.cs b/src/Image.cs index 576104e..3f31c3b 100644 --- a/src/Image.cs +++ b/src/Image.cs @@ -8,7 +8,7 @@ public Image(IDictionary labels) { _labels = labels; } - + public bool IsSnapshot { get; set; } public string? Tag { get; set; } public string Name { get; set; } = null!; @@ -21,7 +21,8 @@ public Image(IDictionary labels) { var imageTag = container.ImageTag; var tagPrefix = container.GetLabel(Constants.TagPrefix); - if (tagPrefix is not null && imageTag?.StartsWith(tagPrefix) == true) imageTag = imageTag[tagPrefix.Length..]; + if (tagPrefix is not null && imageTag?.StartsWith(tagPrefix) == true) + imageTag = imageTag[tagPrefix.Length..]; return container is { Running: true } && imageTag != Tag; }); @@ -46,7 +47,6 @@ public Image? BaseImage public IReadOnlyList Containers { get; set; } = new List(); - public string? GetLabel(string label) => _labels.Where(l => l.Key == label) - .Select(l => l.Value) - .SingleOrDefault(); -} \ No newline at end of file + public string? GetLabel(string label) => + _labels.Where(l => l.Key == label).Select(l => l.Value).SingleOrDefault(); +} diff --git a/src/ImageGroup.cs b/src/ImageGroup.cs index 65e7846..3331ef6 100644 --- a/src/ImageGroup.cs +++ b/src/ImageGroup.cs @@ -23,4 +23,4 @@ public void AddImage(Image image) image.Group = this; _images.Add(image); } -} \ No newline at end of file +} diff --git a/src/ImageIdentifierAndTagEvaluator.cs b/src/ImageIdentifierAndTagEvaluator.cs index e063a3a..b529d79 100644 --- a/src/ImageIdentifierAndTagEvaluator.cs +++ b/src/ImageIdentifierAndTagEvaluator.cs @@ -19,9 +19,11 @@ public ImageIdentifierAndTagEvaluator(port.Config.Config config) var imageConfig = _config.GetImageConfigByIdentifier(identifierAndTag.imageName); if (imageConfig.ImageTags.Count > 1) { - throw new InvalidOperationException("Given identifier has multiple tags, please manually provide the tag"); + throw new InvalidOperationException( + "Given identifier has multiple tags, please manually provide the tag" + ); } return (imageConfig.Identifier, imageConfig.ImageTags.Single()); } -} \ No newline at end of file +} diff --git a/src/ImageIdentifierPrompt.cs b/src/ImageIdentifierPrompt.cs index e8c33bd..47347f2 100644 --- a/src/ImageIdentifierPrompt.cs +++ b/src/ImageIdentifierPrompt.cs @@ -6,7 +6,8 @@ internal class ImageIdentifierPrompt : IImageIdentifierPrompt { private readonly IAllImagesQuery _allImagesQuery; private readonly port.Config.Config _config; - private const string GreyMoveUpAndDownToRevealMoreImages = "[grey](Move up and down to reveal more images)[/]"; + private const string GreyMoveUpAndDownToRevealMoreImages = + "[grey](Move up and down to reveal more images)[/]"; public ImageIdentifierPrompt(IAllImagesQuery allImagesQuery, port.Config.Config config) { @@ -30,60 +31,76 @@ public string GetBaseIdentifierFromUser(string command) return selectedImageConfig.Identifier; } - public async Task<(string identifier, string? tag)> GetBaseIdentifierAndTagFromUserAsync(string command) + public async Task<(string identifier, string? tag)> GetBaseIdentifierAndTagFromUserAsync( + string command + ) { - var groups = await Spinner.StartAsync($"Loading images to [green]{command}[/]", - async _ => await _allImagesQuery.QueryAsync().ToListAsync()); + var groups = await Spinner.StartAsync( + $"Loading images to [green]{command}[/]", + async _ => await _allImagesQuery.QueryAsync().ToListAsync() + ); var lengths = TagTextBuilder.GetLengths(groups.SelectMany(group => group.Images)); var selectionPrompt = CreateSelectionPrompt(command, lengths); foreach (var imageGroup in groups.OrderBy(i => i.Identifier)) { - selectionPrompt.AddChoices(imageGroup.Images - .Where(e => !e.IsSnapshot) - .Where(e => e.Tag != null) - .OrderBy(e => e.Tag)); + selectionPrompt.AddChoices( + imageGroup + .Images.Where(e => !e.IsSnapshot) + .Where(e => e.Tag != null) + .OrderBy(e => e.Tag) + ); } var selectedImage = (Image)AnsiConsole.Prompt(selectionPrompt); return (selectedImage.Group.Identifier, selectedImage.Tag); } - public async Task<(string identifier, string? tag)> GetDownloadedIdentifierAndTagFromUserAsync(string command) + public async Task<(string identifier, string? tag)> GetDownloadedIdentifierAndTagFromUserAsync( + string command + ) { - var groups = await Spinner.StartAsync($"Loading images to [green]{command}[/]", - async _ => await _allImagesQuery.QueryAsync().ToListAsync()); + var groups = await Spinner.StartAsync( + $"Loading images to [green]{command}[/]", + async _ => await _allImagesQuery.QueryAsync().ToListAsync() + ); var lengths = TagTextBuilder.GetLengths(groups.SelectMany(group => group.Images)); var selectionPrompt = CreateSelectionPrompt(command, lengths); foreach (var imageGroup in groups.OrderBy(i => i.Identifier)) { - selectionPrompt.AddChoices(imageGroup.Images - .Where(e => e.Existing) - .OrderBy(e => e.Tag)); + selectionPrompt.AddChoices( + imageGroup.Images.Where(e => e.Existing).OrderBy(e => e.Tag) + ); } var selectedImage = (Image)AnsiConsole.Prompt(selectionPrompt); return (selectedImage.Group.Identifier, selectedImage.Tag); } - public async Task<(string identifier, string? tag)> GetRunnableIdentifierAndTagFromUserAsync(string command) + public async Task<(string identifier, string? tag)> GetRunnableIdentifierAndTagFromUserAsync( + string command + ) { - var groups = await Spinner.StartAsync($"Loading images to [green]{command}[/]", - async _ => await _allImagesQuery.QueryAsync().ToListAsync()); + var groups = await Spinner.StartAsync( + $"Loading images to [green]{command}[/]", + async _ => await _allImagesQuery.QueryAsync().ToListAsync() + ); var lengths = TagTextBuilder.GetLengths(groups.SelectMany(group => group.Images)); var selectionPrompt = CreateSelectionPrompt(command, lengths); foreach (var imageGroup in groups.OrderBy(i => i.Identifier)) { - selectionPrompt.AddChoices(imageGroup.Images - .Where(e => e.Tag != null) - .OrderBy(e => e.Tag)); + selectionPrompt.AddChoices( + imageGroup.Images.Where(e => e.Tag != null).OrderBy(e => e.Tag) + ); } var selectedImage = (Image)AnsiConsole.Prompt(selectionPrompt); return (selectedImage.Group.Identifier, selectedImage.Tag); } - private static SelectionPrompt CreateSelectionPrompt(string command, - (int first, int second) lengths) + private static SelectionPrompt CreateSelectionPrompt( + string command, + (int first, int second) lengths + ) { return new SelectionPrompt() .UseConverter(image => TagTextBuilder.BuildTagText(image, lengths)) @@ -91,4 +108,4 @@ private static SelectionPrompt CreateSelectionPrompt(string command, .Title($"Select image you wish to [green]{command}[/]") .MoreChoicesText(GreyMoveUpAndDownToRevealMoreImages); } -} \ No newline at end of file +} diff --git a/src/ImageNameHelper.cs b/src/ImageNameHelper.cs index 1080081..4a70796 100644 --- a/src/ImageNameHelper.cs +++ b/src/ImageNameHelper.cs @@ -3,18 +3,25 @@ namespace port; public static class ImageNameHelper { private const string Separator = ":"; + public static (string imageName, string? tag) GetImageNameAndTag(string imageName) { var idx = imageName.LastIndexOf(Separator, StringComparison.Ordinal); if (idx == -1) { - throw new ArgumentException($"Does not contain tag separator {Separator}", nameof(imageName)); + throw new ArgumentException( + $"Does not contain tag separator {Separator}", + nameof(imageName) + ); } return (imageName[..idx], imageName[(idx + 1)..]); } - public static bool TryGetImageNameAndTag(string imageName, out (string imageName, string tag) nameAndTag) + public static bool TryGetImageNameAndTag( + string imageName, + out (string imageName, string tag) nameAndTag + ) { nameAndTag = (imageName, string.Empty); var idx = imageName.LastIndexOf(Separator, StringComparison.Ordinal); @@ -27,5 +34,6 @@ public static bool TryGetImageNameAndTag(string imageName, out (string imageName return true; } - public static string BuildImageName(string imageName, string? tag = null) => tag == null ? imageName : $"{imageName}{Separator}{tag}"; -} \ No newline at end of file + public static string BuildImageName(string imageName, string? tag = null) => + tag == null ? imageName : $"{imageName}{Separator}{tag}"; +} diff --git a/src/ImageRemovalResult.cs b/src/ImageRemovalResult.cs index c537bec..acc5c33 100644 --- a/src/ImageRemovalResult.cs +++ b/src/ImageRemovalResult.cs @@ -10,4 +10,4 @@ public ImageRemovalResult(string imageId, bool successful) internal string ImageId { get; } internal bool Successful { get; } -} \ No newline at end of file +} diff --git a/src/Infrastructure/TypeRegistrar.cs b/src/Infrastructure/TypeRegistrar.cs index a128d70..29c4ecb 100644 --- a/src/Infrastructure/TypeRegistrar.cs +++ b/src/Infrastructure/TypeRegistrar.cs @@ -36,4 +36,4 @@ public void RegisterLazy(Type service, Func func) _builder.AddSingleton(service, (provider) => func()); } -} \ No newline at end of file +} diff --git a/src/Infrastructure/TypeResolver.cs b/src/Infrastructure/TypeResolver.cs index 2fac33f..f6749e5 100644 --- a/src/Infrastructure/TypeResolver.cs +++ b/src/Infrastructure/TypeResolver.cs @@ -23,4 +23,4 @@ public void Dispose() disposable.Dispose(); } } -} \ No newline at end of file +} diff --git a/src/Program.cs b/src/Program.cs index 34d8a88..bc0aa36 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -37,15 +37,24 @@ registrations.AddTransient(); registrations.AddTransient(); registrations.AddTransient(); +registrations.AddTransient(); +registrations.AddTransient(); registrations.AddSingleton(typeof(Config), _ => ConfigFactory.GetOrCreateConfig()); -registrations.AddSingleton(typeof(IDockerClient), provider => -{ - var config = provider.GetService(); - if (config?.DockerEndpoint == null) - throw new InvalidOperationException("Docker endpoint has not been configured"); - var endpoint = new Uri(config.DockerEndpoint); - return new DockerClientConfiguration(endpoint, null, TimeSpan.FromSeconds(300)).CreateClient(); -}); +registrations.AddSingleton( + typeof(IDockerClient), + provider => + { + var config = provider.GetService(); + if (config?.DockerEndpoint == null) + throw new InvalidOperationException("Docker endpoint has not been configured"); + var endpoint = new Uri(config.DockerEndpoint); + return new DockerClientConfiguration( + endpoint, + null, + TimeSpan.FromSeconds(300) + ).CreateClient(); + } +); var registrar = new TypeRegistrar(registrations); @@ -54,42 +63,37 @@ app.Configure(appConfig => { appConfig.UseAssemblyInformationalVersion(); - appConfig.AddCommand("pull") - .WithAlias("p"); - appConfig.AddCommand("run") - .WithAlias("r"); - appConfig.AddCommand("reset") - .WithAlias("rs"); - appConfig.AddCommand("commit") - .WithAlias("c"); - appConfig.AddCommand("list") - .WithAlias("ls"); - appConfig.AddCommand("remove") - .WithAlias("rm"); - appConfig.AddCommand("prune") - .WithAlias("pr"); - appConfig.AddCommand("stop") - .WithAlias("s"); - appConfig.AddCommand("config") - .WithAlias("cfg"); + appConfig.AddCommand("pull").WithAlias("p"); + appConfig.AddCommand("run").WithAlias("r"); + appConfig.AddCommand("reset").WithAlias("rs"); + appConfig.AddCommand("commit").WithAlias("c"); + appConfig.AddCommand("list").WithAlias("ls"); + appConfig.AddCommand("remove").WithAlias("rm"); + appConfig.AddCommand("prune").WithAlias("pr"); + appConfig.AddCommand("stop").WithAlias("s"); + appConfig.AddCommand("config").WithAlias("cfg"); }); AnsiConsole.Console = new CustomConsole(); app.Configure(config => { - config.SetExceptionHandler((exception, _) => - { - switch (exception) + config.SetExceptionHandler( + (exception, _) => { - case TimeoutException: - AnsiConsole.MarkupLine("[red]Timeout exception occurred[/], is the Docker daemon running?"); - return -1; - default: - AnsiConsole.WriteException(exception, ExceptionFormats.ShortenEverything); - return -1; + switch (exception) + { + case TimeoutException: + AnsiConsole.MarkupLine( + "[red]Timeout exception occurred[/], is the Docker daemon running?" + ); + return -1; + default: + AnsiConsole.WriteException(exception, ExceptionFormats.ShortenEverything); + return -1; + } } - }); + ); }); -return app.Run(args); \ No newline at end of file +return app.Run(args); diff --git a/src/Progress.cs b/src/Progress.cs index 522dccc..da1324e 100644 --- a/src/Progress.cs +++ b/src/Progress.cs @@ -3,7 +3,7 @@ namespace port; public class Progress { internal const string NullId = nameof(NullId); - + internal Progress(Progress progress) { Id = progress.Id; @@ -36,5 +36,5 @@ public enum ProgressState { Initial, Downloading, - Finished -} \ No newline at end of file + Finished, +} diff --git a/src/ProgressSubscriber.cs b/src/ProgressSubscriber.cs index 2dfecd8..8daa36b 100644 --- a/src/ProgressSubscriber.cs +++ b/src/ProgressSubscriber.cs @@ -12,9 +12,11 @@ public IDisposable Subscribe(Progress progress, IObserver lock (lockObject) { var publishedProgress = new Dictionary(); - return Observable.FromEventPattern( + return Observable + .FromEventPattern( h => progress.ProgressChanged += h, - h => progress.ProgressChanged -= h) + h => progress.ProgressChanged -= h + ) .Subscribe(pattern => { lock (lockObject) @@ -25,8 +27,11 @@ public IDisposable Subscribe(Progress progress, IObserver } } - private static void HandleProgressMessage(JSONMessage message, IDictionary publishedProgress, - IObserver observer) + private static void HandleProgressMessage( + JSONMessage message, + IDictionary publishedProgress, + IObserver observer + ) { if (string.IsNullOrEmpty(message.ID)) message.ID = Progress.NullId; @@ -49,23 +54,28 @@ private static TaskSetUpData CreateTaskSetUpData(JSONMessage message) Description = message.Status, ProgressMessage = message.ProgressMessage, CurrentProgress = message.Progress?.Current, - TotalProgress = message.Progress?.Total + TotalProgress = message.Progress?.Total, }; return data; } - private static void PublishInitialProgress(JSONMessage message, + private static void PublishInitialProgress( + JSONMessage message, IDictionary launchedTasks, - TaskSetUpData data, IObserver progressSubjekt) + TaskSetUpData data, + IObserver progressSubjekt + ) { var progress = new Progress(ProgressState.Initial, message.ID, data); launchedTasks.Add(message.ID, progress); progressSubjekt.OnNext(progress); } - private static void PublishUpdatedProgress(JSONMessage message, + private static void PublishUpdatedProgress( + JSONMessage message, Progress currentProgress, - IObserver progressSubjekt) + IObserver progressSubjekt + ) { var progress = new Progress(currentProgress) { @@ -73,7 +83,7 @@ private static void PublishUpdatedProgress(JSONMessage message, Description = message.Status, ProgressMessage = message.ProgressMessage, CurrentProgress = message.Progress?.Current, - TotalProgress = message.Progress?.Total + TotalProgress = message.Progress?.Total, }; progressSubjekt.OnNext(progress); } @@ -85,4 +95,4 @@ internal class TaskSetUpData public long? CurrentProgress { get; set; } public long? TotalProgress { get; set; } } -} \ No newline at end of file +} diff --git a/src/RemoveImageCommand.cs b/src/RemoveImageCommand.cs index b8a05b7..9b14efa 100644 --- a/src/RemoveImageCommand.cs +++ b/src/RemoveImageCommand.cs @@ -20,16 +20,17 @@ public Task ExecuteAsync(string imageName, string? tag) throw new ArgumentException("Can not remove untagged images"); } - return _dockerClient.Images.DeleteImageAsync(ImageNameHelper.BuildImageName(imageName, tag), - new ImageDeleteParameters()); + return _dockerClient.Images.DeleteImageAsync( + ImageNameHelper.BuildImageName(imageName, tag), + new ImageDeleteParameters() + ); } public async Task ExecuteAsync(string id) { try { - await _dockerClient.Images.DeleteImageAsync(id, - new ImageDeleteParameters()); + await _dockerClient.Images.DeleteImageAsync(id, new ImageDeleteParameters()); return new ImageRemovalResult(id, true); } catch (DockerApiException e) when (e.StatusCode == HttpStatusCode.Conflict) @@ -37,4 +38,4 @@ await _dockerClient.Images.DeleteImageAsync(id, return new ImageRemovalResult(id, false); } } -} \ No newline at end of file +} diff --git a/src/RemoveImagesCliDependentCommand.cs b/src/RemoveImagesCliDependentCommand.cs index caeae91..6aa6e21 100644 --- a/src/RemoveImagesCliDependentCommand.cs +++ b/src/RemoveImagesCliDependentCommand.cs @@ -8,16 +8,21 @@ internal class RemoveImagesCliDependentCommand : IRemoveImagesCliDependentComman private readonly IStopAndRemoveContainerCommand _stopAndRemoveContainerCommand; private readonly IRemoveImageCommand _removeImageCommand; - public RemoveImagesCliDependentCommand(IGetContainersQuery getContainersQuery, - IStopAndRemoveContainerCommand stopAndRemoveContainerCommand, - IRemoveImageCommand removeImageCommand) + public RemoveImagesCliDependentCommand( + IGetContainersQuery getContainersQuery, + IStopAndRemoveContainerCommand stopAndRemoveContainerCommand, + IRemoveImageCommand removeImageCommand + ) { _getContainersQuery = getContainersQuery; _stopAndRemoveContainerCommand = stopAndRemoveContainerCommand; _removeImageCommand = removeImageCommand; } - public async Task> ExecuteAsync(List imageIds, StatusContext ctx) + public async Task> ExecuteAsync( + List imageIds, + StatusContext ctx + ) { var result = new List(); foreach (var imageId in imageIds) @@ -36,4 +41,4 @@ public async Task> ExecuteAsync(List imageIds, return result; } -} \ No newline at end of file +} diff --git a/src/RunContainerCommand.cs b/src/RunContainerCommand.cs index 7a225d1..ac3f84a 100644 --- a/src/RunContainerCommand.cs +++ b/src/RunContainerCommand.cs @@ -14,10 +14,7 @@ public RunContainerCommand(IDockerClient dockerClient) public Task ExecuteAsync(string id) { - return _dockerClient.Containers.StartContainerAsync( - id, - new ContainerStartParameters() - ); + return _dockerClient.Containers.StartContainerAsync(id, new ContainerStartParameters()); } public Task ExecuteAsync(Container container) @@ -27,4 +24,4 @@ public Task ExecuteAsync(Container container) new ContainerStartParameters() ); } -} \ No newline at end of file +} diff --git a/src/Spectre/CustomConsole.cs b/src/Spectre/CustomConsole.cs index 311a642..51cccc0 100644 --- a/src/Spectre/CustomConsole.cs +++ b/src/Spectre/CustomConsole.cs @@ -23,4 +23,4 @@ public CustomConsole() public IExclusivityMode ExclusivityMode => _console.ExclusivityMode; public RenderPipeline Pipeline => _console.Pipeline; -} \ No newline at end of file +} diff --git a/src/Spectre/CustomConsoleInput.cs b/src/Spectre/CustomConsoleInput.cs index 838cb77..0c1f9bf 100644 --- a/src/Spectre/CustomConsoleInput.cs +++ b/src/Spectre/CustomConsoleInput.cs @@ -13,22 +13,38 @@ public CustomConsoleInput(IAnsiConsoleInput input) public bool IsKeyAvailable() => _input.IsKeyAvailable(); - public ConsoleKeyInfo? ReadKey(bool intercept) => RewriteConsoleKeyInfo(_input.ReadKey(intercept)); + public ConsoleKeyInfo? ReadKey(bool intercept) => + RewriteConsoleKeyInfo(_input.ReadKey(intercept)); - public async Task ReadKeyAsync(bool intercept, CancellationToken cancellationToken) => - RewriteConsoleKeyInfo(await _input.ReadKeyAsync(intercept, cancellationToken)); + public async Task ReadKeyAsync( + bool intercept, + CancellationToken cancellationToken + ) => RewriteConsoleKeyInfo(await _input.ReadKeyAsync(intercept, cancellationToken)); private static ConsoleKeyInfo? RewriteConsoleKeyInfo(ConsoleKeyInfo? keyInfo) { - if (keyInfo?.Key is not (ConsoleKey.J or ConsoleKey.K)) return keyInfo; + if (keyInfo?.Key is not (ConsoleKey.J or ConsoleKey.K)) + return keyInfo; var shift = keyInfo.Value.Modifiers == ConsoleModifiers.Shift; var alt = keyInfo.Value.Modifiers == ConsoleModifiers.Alt; var control = keyInfo.Value.Modifiers == ConsoleModifiers.Control; return keyInfo.Value.Key switch { - ConsoleKey.J => new ConsoleKeyInfo(keyInfo.Value.KeyChar, ConsoleKey.DownArrow, shift, alt, control), - ConsoleKey.K => new ConsoleKeyInfo(keyInfo.Value.KeyChar, ConsoleKey.UpArrow, shift, alt, control), - _ => throw new ArgumentOutOfRangeException() + ConsoleKey.J => new ConsoleKeyInfo( + keyInfo.Value.KeyChar, + ConsoleKey.DownArrow, + shift, + alt, + control + ), + ConsoleKey.K => new ConsoleKeyInfo( + keyInfo.Value.KeyChar, + ConsoleKey.UpArrow, + shift, + alt, + control + ), + _ => throw new ArgumentOutOfRangeException(), }; } -} \ No newline at end of file +} diff --git a/src/Spinner.cs b/src/Spinner.cs index 84aebf7..246f461 100644 --- a/src/Spinner.cs +++ b/src/Spinner.cs @@ -6,16 +6,25 @@ internal static class Spinner { public static T Start(string status, Func func) { - return AnsiConsole.Status().Spinner(global::Spectre.Console.Spinner.Known.Dots).Start(status, func); + return AnsiConsole + .Status() + .Spinner(global::Spectre.Console.Spinner.Known.Dots) + .Start(status, func); } public static Task StartAsync(string status, Func action) { - return AnsiConsole.Status().Spinner(global::Spectre.Console.Spinner.Known.Dots).StartAsync(status, action); + return AnsiConsole + .Status() + .Spinner(global::Spectre.Console.Spinner.Known.Dots) + .StartAsync(status, action); } public static Task StartAsync(string status, Func> func) { - return AnsiConsole.Status().Spinner(global::Spectre.Console.Spinner.Known.Dots).StartAsync(status, func); + return AnsiConsole + .Status() + .Spinner(global::Spectre.Console.Spinner.Known.Dots) + .StartAsync(status, func); } -} \ No newline at end of file +} diff --git a/src/StopAndRemoveContainerCommand.cs b/src/StopAndRemoveContainerCommand.cs index 97c4a4a..9511f26 100644 --- a/src/StopAndRemoveContainerCommand.cs +++ b/src/StopAndRemoveContainerCommand.cs @@ -8,7 +8,10 @@ internal class StopAndRemoveContainerCommand : IStopAndRemoveContainerCommand private readonly IStopContainerCommand _stopContainerCommand; private readonly IDockerClient _dockerClient; - public StopAndRemoveContainerCommand(IDockerClient dockerClient, IStopContainerCommand stopContainerCommand) + public StopAndRemoveContainerCommand( + IDockerClient dockerClient, + IStopContainerCommand stopContainerCommand + ) { _dockerClient = dockerClient; _stopContainerCommand = stopContainerCommand; @@ -17,6 +20,9 @@ public StopAndRemoveContainerCommand(IDockerClient dockerClient, IStopContainerC public async Task ExecuteAsync(string containerId) { await _stopContainerCommand.ExecuteAsync(containerId); - await _dockerClient.Containers.RemoveContainerAsync(containerId, new ContainerRemoveParameters()); + await _dockerClient.Containers.RemoveContainerAsync( + containerId, + new ContainerRemoveParameters() + ); } -} \ No newline at end of file +} diff --git a/src/StopContainerCommand.cs b/src/StopContainerCommand.cs index 1b8fc6f..c2e9629 100644 --- a/src/StopContainerCommand.cs +++ b/src/StopContainerCommand.cs @@ -13,5 +13,8 @@ public StopContainerCommand(IDockerClient dockerClient) } public async Task ExecuteAsync(string containerId) => - await _dockerClient.Containers.StopContainerAsync(containerId, new ContainerStopParameters()); -} \ No newline at end of file + await _dockerClient.Containers.StopContainerAsync( + containerId, + new ContainerStopParameters() + ); +} diff --git a/src/TagPrefixHelper.cs b/src/TagPrefixHelper.cs index 7a658ad..c62f88b 100644 --- a/src/TagPrefixHelper.cs +++ b/src/TagPrefixHelper.cs @@ -4,8 +4,9 @@ namespace port; internal static partial class TagPrefixHelper { - public static string GetTagPrefix(string identifier) => ContainerIdentifierSanitizerRegex().Replace(identifier, string.Empty).ToLower(); + public static string GetTagPrefix(string identifier) => + ContainerIdentifierSanitizerRegex().Replace(identifier, string.Empty).ToLower(); [GeneratedRegex(@"[^a-zA-Z0-9_-]")] private static partial Regex ContainerIdentifierSanitizerRegex(); -} \ No newline at end of file +} diff --git a/src/TagTextBuilder.cs b/src/TagTextBuilder.cs index 0e3a216..97daf28 100644 --- a/src/TagTextBuilder.cs +++ b/src/TagTextBuilder.cs @@ -13,9 +13,11 @@ public static (int first, int second) GetLengths(IEnumerable images) foreach (var image in images) { var f = BuildFirstLine(image).RemoveMarkup().Length; - if (first < f) first = f; + if (first < f) + first = f; var s = BuildSecondLine(image).RemoveMarkup().Length; - if (second < s) second = s; + if (second < s) + second = s; } return (first, second); @@ -38,13 +40,17 @@ public static string BuildTagText(Image image, (int first, int second) lengths) } var firstLine = BuildFirstLine(image); - sb.Append(firstLine.PadRight(lengths.first + firstLine.Length - firstLine.RemoveMarkup().Length)); + sb.Append( + firstLine.PadRight(lengths.first + firstLine.Length - firstLine.RemoveMarkup().Length) + ); var secondLine = BuildSecondLine(image); if (!string.IsNullOrWhiteSpace(secondLine)) { AddSeparator(sb); sb.Append("[dim]"); - sb.Append($"{secondLine.PadRight(lengths.second + secondLine.Length - secondLine.RemoveMarkup().Length)}"); + sb.Append( + $"{secondLine.PadRight(lengths.second + secondLine.Length - secondLine.RemoveMarkup().Length)}" + ); sb.Append("[/]"); } @@ -111,7 +117,8 @@ private static string BuildThirdLine(Image image) foreach (var container in image.Containers) { sb.Append( - $"Container: {container.Created.ToLocalTime().ToString(CultureInfo.CurrentCulture)}"); + $"Container: {container.Created.ToLocalTime().ToString(CultureInfo.CurrentCulture)}" + ); } return sb.ToString(); @@ -121,4 +128,4 @@ private static void AddSeparator(StringBuilder sb) { sb.Append("[dim] | [/]"); } -} \ No newline at end of file +} diff --git a/src/port.csproj b/src/port.csproj index f2bc4a6..fd35e3e 100644 --- a/src/port.csproj +++ b/src/port.csproj @@ -1,22 +1,19 @@ - - - Exe - net9.0 - enable - enable - true - - - - - - - - - - - - - + + Exe + net9.0 + enable + enable + true + + + + + + + + + + +