Skip to content

Commit 71c6b92

Browse files
committed
feat: Integrate command chain detection into all commands
- Register ICommandChainDetector and ConditionalListCliCommand in DI container - Update all CLI commands to use ConditionalListCliCommand instead of ListCliCommand - Commands affected: Pull, Run, Prune, Commit, Stop, Reset, Remove - Chain detection now controls when list output is displayed automatically
1 parent 669911a commit 71c6b92

23 files changed

+453
-305
lines changed

src/Commands/Commit/CommitCliCommand.cs

Lines changed: 97 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -14,83 +14,108 @@ internal class CommitCliCommand(
1414
IGetDigestsByIdQuery getDigestsByIdQuery,
1515
IGetContainersQuery getContainersQuery,
1616
IStopAndRemoveContainerCommand stopAndRemoveContainerCommand,
17-
ListCliCommand listCliCommand)
18-
: AsyncCommand<CommitSettings>
17+
ConditionalListCliCommand conditionalListCliCommand
18+
) : AsyncCommand<CommitSettings>
1919
{
2020
public override async Task<int> ExecuteAsync(CommandContext context, CommitSettings settings)
2121
{
22-
var container = await GetContainerAsync(settings) ??
23-
throw new InvalidOperationException("No running container found");
22+
var container =
23+
await GetContainerAsync(settings)
24+
?? throw new InvalidOperationException("No running container found");
2425

25-
await Spinner.StartAsync("Committing container", async ctx =>
26-
{
27-
string newTag;
28-
string imageName;
29-
string tagPrefix;
30-
if (settings.Overwrite)
31-
{
32-
newTag = container.ImageTag ??
33-
throw new InvalidOperationException(
34-
"When using --overwrite, container must have an image tag");
35-
imageName = container.ImageIdentifier;
36-
tagPrefix = container.TagPrefix;
37-
}
38-
else
26+
await Spinner.StartAsync(
27+
"Committing container",
28+
async ctx =>
3929
{
40-
var tag = settings.Tag ?? $"{DateTime.Now:yyyyMMddhhmmss}";
41-
(imageName, tagPrefix, newTag) = await GetNewTagAsync(container, tag);
42-
}
43-
44-
45-
ctx.Status = $"Looking for existing container named '{container.ContainerName}'";
46-
var containerWithSameTag = await getContainersQuery
47-
.QueryByContainerIdentifierAndTagAsync(container.ContainerIdentifier, newTag)
48-
.ToListAsync();
49-
50-
ctx.Status = $"Creating image from running container '{container.ContainerName}'";
51-
newTag = await createImageFromContainerCommand.ExecuteAsync(container, imageName, tagPrefix, newTag);
52-
53-
ctx.Status = $"Removing containers named '{container.ContainerName}'";
54-
await Task.WhenAll(containerWithSameTag.Select(async container1 =>
55-
await stopAndRemoveContainerCommand.ExecuteAsync(container1.Id)));
56-
57-
if (settings.Overwrite)
58-
{
59-
if (newTag == null)
60-
throw new InvalidOperationException("newTag is null");
61-
62-
if (container.ImageTag == null)
63-
throw new InvalidOperationException(
64-
"Switch argument not supported when creating image from untagged container");
65-
66-
ctx.Status = "Launching new image";
67-
var id = await createContainerCommand.ExecuteAsync(container, tagPrefix, newTag);
68-
await runContainerCommand.ExecuteAsync(id);
69-
}
70-
else if (settings.Switch)
71-
{
72-
if (newTag == null)
73-
throw new InvalidOperationException("newTag is null");
74-
75-
if (container.ImageTag == null)
76-
throw new InvalidOperationException(
77-
"Switch argument not supported when creating image from untagged container");
78-
79-
ctx.Status = $"Stopping running container '{container.ContainerName}'";
80-
await stopContainerCommand.ExecuteAsync(container.Id);
81-
82-
ctx.Status = "Launching new image";
83-
var id = await createContainerCommand.ExecuteAsync(container, tagPrefix, newTag);
84-
await runContainerCommand.ExecuteAsync(id);
30+
string newTag;
31+
string imageName;
32+
string tagPrefix;
33+
if (settings.Overwrite)
34+
{
35+
newTag =
36+
container.ImageTag
37+
?? throw new InvalidOperationException(
38+
"When using --overwrite, container must have an image tag"
39+
);
40+
imageName = container.ImageIdentifier;
41+
tagPrefix = container.TagPrefix;
42+
}
43+
else
44+
{
45+
var tag = settings.Tag ?? $"{DateTime.Now:yyyyMMddhhmmss}";
46+
(imageName, tagPrefix, newTag) = await GetNewTagAsync(container, tag);
47+
}
48+
49+
ctx.Status = $"Looking for existing container named '{container.ContainerName}'";
50+
var containerWithSameTag = await getContainersQuery
51+
.QueryByContainerIdentifierAndTagAsync(container.ContainerIdentifier, newTag)
52+
.ToListAsync();
53+
54+
ctx.Status = $"Creating image from running container '{container.ContainerName}'";
55+
newTag = await createImageFromContainerCommand.ExecuteAsync(
56+
container,
57+
imageName,
58+
tagPrefix,
59+
newTag
60+
);
61+
62+
ctx.Status = $"Removing containers named '{container.ContainerName}'";
63+
await Task.WhenAll(
64+
containerWithSameTag.Select(async container1 =>
65+
await stopAndRemoveContainerCommand.ExecuteAsync(container1.Id)
66+
)
67+
);
68+
69+
if (settings.Overwrite)
70+
{
71+
if (newTag == null)
72+
throw new InvalidOperationException("newTag is null");
73+
74+
if (container.ImageTag == null)
75+
throw new InvalidOperationException(
76+
"Switch argument not supported when creating image from untagged container"
77+
);
78+
79+
ctx.Status = "Launching new image";
80+
var id = await createContainerCommand.ExecuteAsync(
81+
container,
82+
tagPrefix,
83+
newTag
84+
);
85+
await runContainerCommand.ExecuteAsync(id);
86+
}
87+
else if (settings.Switch)
88+
{
89+
if (newTag == null)
90+
throw new InvalidOperationException("newTag is null");
91+
92+
if (container.ImageTag == null)
93+
throw new InvalidOperationException(
94+
"Switch argument not supported when creating image from untagged container"
95+
);
96+
97+
ctx.Status = $"Stopping running container '{container.ContainerName}'";
98+
await stopContainerCommand.ExecuteAsync(container.Id);
99+
100+
ctx.Status = "Launching new image";
101+
var id = await createContainerCommand.ExecuteAsync(
102+
container,
103+
tagPrefix,
104+
newTag
105+
);
106+
await runContainerCommand.ExecuteAsync(id);
107+
}
85108
}
86-
});
109+
);
87110

88-
await listCliCommand.ExecuteAsync();
111+
await conditionalListCliCommand.ExecuteAsync();
89112
return 0;
90113
}
91114

92-
private async Task<(string imageName, string tagPrefix, string newTag)> GetNewTagAsync(Container container,
93-
string tag)
115+
private async Task<(string imageName, string tagPrefix, string newTag)> GetNewTagAsync(
116+
Container container,
117+
string tag
118+
)
94119
{
95120
var image = await getImageQuery.QueryAsync(container.ImageIdentifier, container.ImageTag);
96121
string imageName;
@@ -101,7 +126,8 @@ await Task.WhenAll(containerWithSameTag.Select(async container1 =>
101126
var digest = digests?.SingleOrDefault();
102127
if (digest == null || !DigestHelper.TryGetImageNameAndId(digest, out var nameNameAndId))
103128
throw new InvalidOperationException(
104-
$"Unable to determine image name from running container '{container.ContainerName}'");
129+
$"Unable to determine image name from running container '{container.ContainerName}'"
130+
);
105131
imageName = nameNameAndId.imageName;
106132
}
107133
else
@@ -114,7 +140,8 @@ await Task.WhenAll(containerWithSameTag.Select(async container1 =>
114140

115141
var tagPrefix = container.TagPrefix;
116142
var newTag = baseTag == null ? tag : $"{tagPrefix}{baseTag}-{tag}";
117-
if (newTag.Contains('.')) throw new ArgumentException("only [a-zA-Z0-9][a-zA-Z0-9_-] are allowed");
143+
if (newTag.Contains('.'))
144+
throw new ArgumentException("only [a-zA-Z0-9][a-zA-Z0-9_-] are allowed");
118145
return (imageName, tagPrefix, newTag);
119146
}
120147

@@ -129,4 +156,4 @@ await Task.WhenAll(containerWithSameTag.Select(async container1 =>
129156
var identifier = containerNamePrompt.GetIdentifierOfContainerFromUser(containers, "commit");
130157
return containers.SingleOrDefault(c => c.ContainerName == identifier);
131158
}
132-
}
159+
}

src/Commands/Commit/CommitSettings.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ public class CommitSettings : CommandSettings, IContainerIdentifierSettings
77
[CommandArgument(0, "[ContainerIdentifier]")]
88
public string? ContainerIdentifier { get; set; }
99

10-
[CommandOption("-t|--tag")]
10+
[CommandOption("-t|--tag")]
1111
public string? Tag { get; set; }
1212

13-
[CommandOption("-s|--switch")]
13+
[CommandOption("-s|--switch")]
1414
public bool Switch { get; set; }
1515

16-
[CommandOption("-o|--overwrite")]
16+
[CommandOption("-o|--overwrite")]
1717
public bool Overwrite { get; set; }
18-
}
18+
}

src/Commands/Commit/CreateImageFromContainerCommand.cs

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,35 @@
33

44
namespace port.Commands.Commit;
55

6-
internal class CreateImageFromContainerCommand(IDockerClient dockerClient) : ICreateImageFromContainerCommand
6+
internal class CreateImageFromContainerCommand(IDockerClient dockerClient)
7+
: ICreateImageFromContainerCommand
78
{
8-
public async Task<string> ExecuteAsync(Container container, string imageName, string tagPrefix, string newTag)
9+
public async Task<string> ExecuteAsync(
10+
Container container,
11+
string imageName,
12+
string tagPrefix,
13+
string newTag
14+
)
915
{
1016
var labels = new Dictionary<string, string>();
1117
var identifier = container.GetLabel(Constants.IdentifierLabel);
12-
if (identifier is not null) labels.Add(Constants.IdentifierLabel, identifier);
18+
if (identifier is not null)
19+
labels.Add(Constants.IdentifierLabel, identifier);
1320
var baseTag = container.GetLabel(Constants.BaseTagLabel);
14-
if (baseTag is not null) labels.Add(Constants.BaseTagLabel, baseTag);
21+
if (baseTag is not null)
22+
labels.Add(Constants.BaseTagLabel, baseTag);
1523
labels.Add(Constants.TagPrefix, tagPrefix);
16-
if (baseTag == newTag) throw new InvalidOperationException("Can not overwrite base tags");
17-
await dockerClient.Images.CommitContainerChangesAsync(new CommitContainerChangesParameters
18-
{
19-
ContainerID = container.Id,
20-
RepositoryName = imageName,
21-
Tag = newTag,
22-
Config = new Docker.DotNet.Models.Config
24+
if (baseTag == newTag)
25+
throw new InvalidOperationException("Can not overwrite base tags");
26+
await dockerClient.Images.CommitContainerChangesAsync(
27+
new CommitContainerChangesParameters
2328
{
24-
Labels = labels
29+
ContainerID = container.Id,
30+
RepositoryName = imageName,
31+
Tag = newTag,
32+
Config = new Docker.DotNet.Models.Config { Labels = labels },
2533
}
26-
});
34+
);
2735
return newTag;
2836
}
29-
}
37+
}

src/Commands/Commit/GetDigestsByIdQuery.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,10 @@ public GetDigestsByIdQuery(IDockerClient dockerClient)
1616
{
1717
var parameters = new ImagesListParameters
1818
{
19-
Filters = new Dictionary<string, IDictionary<string, bool>>()
19+
Filters = new Dictionary<string, IDictionary<string, bool>>(),
2020
};
2121
var imagesListResponses = await _dockerClient.Images.ListImagesAsync(parameters);
22-
var imagesListResponse = imagesListResponses
23-
.SingleOrDefault(e => e.ID == imageId);
22+
var imagesListResponse = imagesListResponses.SingleOrDefault(e => e.ID == imageId);
2423
return imagesListResponse?.RepoDigests;
2524
}
26-
}
25+
}

src/Commands/Commit/ICreateImageFromContainerCommand.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,10 @@ namespace port.Commands.Commit;
22

33
public interface ICreateImageFromContainerCommand
44
{
5-
Task<string> ExecuteAsync(Container container, string imageName, string tagPrefix, string newTag);
6-
}
5+
Task<string> ExecuteAsync(
6+
Container container,
7+
string imageName,
8+
string tagPrefix,
9+
string newTag
10+
);
11+
}

src/Commands/Commit/IGetDigestsByIdQuery.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ namespace port.Commands.Commit;
33
internal interface IGetDigestsByIdQuery
44
{
55
Task<IList<string>?> QueryAsync(string imageId);
6-
}
6+
}

src/Commands/Config/ConfigCliCommand.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,6 @@ public override int Execute(CommandContext context, ConfigSettings settings)
2222
return 0;
2323
}
2424

25-
private static string FormatAsLink(string caption, string url) => $"\u001B]8;;{url}\a{caption}\u001B]8;;\a";
26-
}
25+
private static string FormatAsLink(string caption, string url) =>
26+
$"\u001B]8;;{url}\a{caption}\u001B]8;;\a";
27+
}

src/Commands/Config/ConfigSettings.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ internal class ConfigSettings : CommandSettings
66
{
77
[CommandOption("-o|--open")]
88
public bool Open { get; set; } = false;
9-
}
9+
}

src/Commands/List/ListCliCommand.cs

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ public ListCliCommand(IAllImagesQuery allImagesQuery)
1414

1515
public override async Task<int> ExecuteAsync(CommandContext _, ListSettings settings)
1616
{
17-
var textsGroups = await Spinner.StartAsync("Loading images",
18-
async _ => await CreateImageTree(settings.ImageIdentifier).ToListAsync());
17+
var textsGroups = await Spinner.StartAsync(
18+
"Loading images",
19+
async _ => await CreateImageTree(settings.ImageIdentifier).ToListAsync()
20+
);
1921
AnsiConsole.WriteLine();
2022
foreach (var text in textsGroups.SelectMany(texts => texts))
2123
{
@@ -27,7 +29,10 @@ public override async Task<int> ExecuteAsync(CommandContext _, ListSettings sett
2729

2830
public async Task ExecuteAsync()
2931
{
30-
var textsGroups = await Spinner.StartAsync("Loading images", async _ => await CreateImageTree().ToListAsync());
32+
var textsGroups = await Spinner.StartAsync(
33+
"Loading images",
34+
async _ => await CreateImageTree().ToListAsync()
35+
);
3136
AnsiConsole.WriteLine();
3237
foreach (var text in textsGroups.SelectMany(texts => texts))
3338
{
@@ -37,16 +42,20 @@ public async Task ExecuteAsync()
3742

3843
private async IAsyncEnumerable<List<string>> CreateImageTree(string? imageIdentifier = default)
3944
{
40-
var imageGroups = (await _allImagesQuery.QueryAsync().ToListAsync()).Where(e =>
41-
imageIdentifier == null || e.Identifier == imageIdentifier)
42-
.OrderBy(i => i.Identifier).ToList();
43-
var lengths = TagTextBuilder.GetLengths(imageGroups.SelectMany(imageGroup => imageGroup.Images));
45+
var imageGroups = (await _allImagesQuery.QueryAsync().ToListAsync())
46+
.Where(e => imageIdentifier == null || e.Identifier == imageIdentifier)
47+
.OrderBy(i => i.Identifier)
48+
.ToList();
49+
var lengths = TagTextBuilder.GetLengths(
50+
imageGroups.SelectMany(imageGroup => imageGroup.Images)
51+
);
4452
foreach (var imageGroup in imageGroups)
4553
{
46-
yield return imageGroup.Images.Where(e => e.Tag != null)
54+
yield return imageGroup
55+
.Images.Where(e => e.Tag != null)
4756
.OrderBy(e => e.Tag)
4857
.Select(image => TagTextBuilder.BuildTagText(image, lengths))
4958
.ToList();
5059
}
5160
}
52-
}
61+
}

src/Commands/List/ListSettings.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ namespace port.Commands.List;
44

55
public class ListSettings : CommandSettings, IImageIdentifierSettings
66
{
7-
[CommandArgument(0, "[ImageIdentifier]")]
7+
[CommandArgument(0, "[ImageIdentifier]")]
88
public string? ImageIdentifier { get; set; }
9-
}
9+
}

0 commit comments

Comments
 (0)