diff --git a/src/LclDckr/Commands/Ps/ContainerInfo.cs b/src/LclDckr/Commands/Ps/ContainerInfo.cs new file mode 100644 index 0000000..bc89638 --- /dev/null +++ b/src/LclDckr/Commands/Ps/ContainerInfo.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace LclDckr.Commands.Ps +{ + public class ContainerInfo + { + public string ContainerId { get; set; } + public string Image { get; set; } + public string Command { get; set; } + public string Created { get; set; } + public string Status { get; set; } + public string Ports { get; set; } + public List Names { get; set; } + } +} diff --git a/src/LclDckr/Commands/Ps/ContainerInfoParser.cs b/src/LclDckr/Commands/Ps/ContainerInfoParser.cs new file mode 100644 index 0000000..b315c14 --- /dev/null +++ b/src/LclDckr/Commands/Ps/ContainerInfoParser.cs @@ -0,0 +1,58 @@ +using System; +using System.Linq; +using System.Text.RegularExpressions; + +namespace LclDckr.Commands.Ps +{ + /// + /// Parses output of Ps command + /// + internal class ContainerInfoParser + { + private readonly string[] _expectedHeaders = + { + "CONTAINER ID", + "IMAGE", + "COMMAND", + "CREATED", + "STATUS", + "PORTS", + "NAMES" + }; + + private readonly Tuple[] _fieldLocations; + + public ContainerInfoParser(string headers) + { + _fieldLocations = new Tuple[_expectedHeaders.Length]; + + for (int i = 0; i < _expectedHeaders.Length; i++) + { + var idRegex = new Regex($"({_expectedHeaders[i]}) *"); + var match = idRegex.Match(headers); + if (!match.Success) + { + throw new Exception($"Returned headers from ps command did not match expected headers {headers}"); + } + + _fieldLocations[i] = Tuple.Create(match.Index, match.Length); + } + } + + public ContainerInfo Parse(string fields) + { + Func getField = i => fields.Substring(_fieldLocations[i].Item1, i < _fieldLocations.Length - 1 ? _fieldLocations[i].Item2 : fields.Length - _fieldLocations[i].Item1); + + return new ContainerInfo + { + ContainerId = getField(0).Trim(), + Image = getField(1).Trim(), + Command = getField(2).Trim(), + Created = getField(3).Trim(), + Status = getField(4).Trim(), + Ports = getField(5).Trim(), + Names = getField(6).Trim().Split(',').ToList() + }; + } + } +} \ No newline at end of file diff --git a/src/LclDckr/Commands/Ps/Filters/IFilter.cs b/src/LclDckr/Commands/Ps/Filters/IFilter.cs new file mode 100644 index 0000000..5f33d4c --- /dev/null +++ b/src/LclDckr/Commands/Ps/Filters/IFilter.cs @@ -0,0 +1,7 @@ +namespace LclDckr.Commands.Ps.Filters +{ + public interface IFilter + { + string Value { get; } + } +} diff --git a/src/LclDckr/Commands/Ps/Filters/NameFilter.cs b/src/LclDckr/Commands/Ps/Filters/NameFilter.cs new file mode 100644 index 0000000..ccb96b2 --- /dev/null +++ b/src/LclDckr/Commands/Ps/Filters/NameFilter.cs @@ -0,0 +1,18 @@ +namespace LclDckr.Commands.Ps.Filters +{ + public class NameFilter : IFilter + { + public string Name { get; set; } + public string Value => $"name={Name}"; + + public NameFilter() + { + + } + + public NameFilter(string name) + { + Name = name; + } + } +} diff --git a/src/LclDckr/DockerClient.cs b/src/LclDckr/DockerClient.cs index a135e35..42d4728 100644 --- a/src/LclDckr/DockerClient.cs +++ b/src/LclDckr/DockerClient.cs @@ -1,5 +1,9 @@ -using System.Diagnostics; +using System.Collections.Generic; +using System.Diagnostics; using System.Linq; +using System.Text; +using LclDckr.Commands.Ps; +using LclDckr.Commands.Ps.Filters; namespace LclDckr { @@ -21,6 +25,14 @@ public string Build(string path = ".") .Last(); } + /// + /// Runs the specified image in a new container + /// + /// + /// + /// + /// + /// The long uuid of the created container public string RunImage(string imageName, string name, string hostName = null, bool interactive = false) { var hostArg = hostName != null ? $"--hostname {hostName}" : ""; @@ -36,7 +48,12 @@ public string RunImage(string imageName, string name, string hostName = null, bo return process.StandardOutput.ReadToEnd(); } - public void PullImage(string imageName, string tag) + /// + /// Pulls the specified image. + /// + /// + /// + public void PullImage(string imageName, string tag = "latest") { var args = $"pull {imageName}:{tag}"; @@ -46,6 +63,11 @@ public void PullImage(string imageName, string tag) process.ThrowForError(); } + /// + /// starts an existing container by name + /// + /// + /// the name of the started container public string StartContainer(string name) { var args = $"start {name}"; @@ -58,6 +80,11 @@ public string StartContainer(string name) return process.StandardOutput.ReadToEnd(); } + /// + /// stops an existing container by name + /// + /// + /// the name of the stopped container public string StopContainer(string name) { var args = $"stop {name}"; @@ -69,6 +96,11 @@ public string StopContainer(string name) return process.StandardOutput.ReadToEnd(); } + /// + /// removes an existing container by name + /// + /// + /// public string RemoveContainer(string name) { var args = $"rm {name}"; @@ -80,6 +112,51 @@ public string RemoveContainer(string name) return process.StandardOutput.ReadToEnd(); } + /// + /// Returns info on this systems containers + /// + /// true for all containers, false for running containers only + /// + /// + public ICollection Ps(bool all = false, IEnumerable filters = null) + { + var args = new StringBuilder("ps"); + + if (all) + { + args.Append(" -a"); + } + + if (filters != null) + { + foreach (var filter in filters) + { + args.Append($" --filter \"{filter.Value}\""); + } + } + + var process = GetDockerProcess(args.ToString()); + + process.Start(); + process.WaitForExit(); + process.ThrowForError(); + + var headers = process.StandardOutput.ReadLine(); + + var parser = new ContainerInfoParser(headers); + + var containers = new List(); + + while (!process.StandardOutput.EndOfStream) + { + var fields = process.StandardOutput.ReadLine(); + var container = parser.Parse(fields); + containers.Add(container); + } + + return containers; + } + private Process GetDockerProcess(string arguments) { return new Process diff --git a/src/LclDckr/project.json b/src/LclDckr/project.json index c2637ae..ddd210c 100644 --- a/src/LclDckr/project.json +++ b/src/LclDckr/project.json @@ -1,8 +1,14 @@ { - "version": "1.0.0-*", + "version": "1.0.1-*", "description": "A dotnet wrapper around the docker cli.", - "authors": ["Syncromatics"], - + "authors": ["Syncromatics"], + "packOptions": { + "tags": [ + "Docker", + "Local Docker", + "Docker Cli" + ] + }, "dependencies": { }, diff --git a/test/LclDckr.IntegrationTests/DockerClientTests.cs b/test/LclDckr.IntegrationTests/DockerClientTests.cs index d96467f..073eafe 100644 --- a/test/LclDckr.IntegrationTests/DockerClientTests.cs +++ b/test/LclDckr.IntegrationTests/DockerClientTests.cs @@ -1,4 +1,6 @@ -using Xunit; +using System.Linq; +using LclDckr.Commands.Ps.Filters; +using Xunit; namespace LclDckr.IntegrationTests { @@ -8,15 +10,27 @@ public class DockerClientTests public void Pulls_runs_stops_image() { var client = new DockerClient(); - client.PullImage("ubuntu", "latest"); + client.PullImage("ubuntu"); - var id = client.RunImage("ubuntu", "test-container", interactive: true); + var containerName = "lcldckr-test-container"; + + var id = client.RunImage("ubuntu", containerName, interactive: true); Assert.NotNull(id); - id = client.StopContainer("test-container"); + var runningContainer = client + .Ps(true, new[] {new NameFilter(containerName)}) + .SingleOrDefault(); + + Assert.NotNull(runningContainer); + + Assert.Equal(containerName, runningContainer.Names.Single()); + + Assert.Equal(id.Substring(0, 12), runningContainer.ContainerId); + + id = client.StopContainer(containerName); Assert.NotNull(id); - id = client.RemoveContainer("test-container"); + id = client.RemoveContainer(containerName); Assert.NotNull(id); } } diff --git a/test/LclDckr.IntegrationTests/project.json b/test/LclDckr.IntegrationTests/project.json index 4f3ca3c..f2a5707 100644 --- a/test/LclDckr.IntegrationTests/project.json +++ b/test/LclDckr.IntegrationTests/project.json @@ -3,7 +3,7 @@ "testRunner": "xunit", "dependencies": { "xunit": "2.2.0-beta4-build3444", - "LclDckr": "1.0.0-*", + "LclDckr": "1.0.1-*", "dotnet-test-xunit": "2.2.0-preview2-build1029" }, "frameworks": {