1 module viva.docker.docker;
2 
3 import viva.io;
4 import std.file;
5 import std.conv;
6 import std.array;
7 import std.getopt;
8 import std.process;
9 import std.algorithm;
10 
11 /++
12  + struct holding options for a docker operation
13  +/
14 public struct DockerOptions
15 {
16     /// the docker image to be run
17     string image = null;
18     /// custom command to be run
19     string command = null;
20     /// container user
21     string user = null;
22     /// container name
23     string name = null;
24 
25     /// verbose mode
26     bool verbose = false;
27     /// detach the container
28     bool detach = false;
29     /// remove container
30     bool remove = true;
31     /// enable privileged mode (useful for GDB)
32     bool privileged = false;
33     /// enable GDB (GNU Debugger)
34     bool gdb = false;
35 }
36 
37 /++
38  + struct holding info about a container
39  +/
40 public struct DockerContainerInfo
41 {
42     /// the id of the container
43     string id;
44     
45     /// the name of the container
46     string name;
47     
48     /// the image name of the container
49     string image;
50     
51     /// the status of the container (`Exited` or `Created`)
52     string status;
53 }
54 
55 // TODO: maybe we shouldn't add the sudo? and have the user run the d project with sudo instead? so its not there if not needed?
56 version(Windows) private string[] defaultArgs = ["docker"];
57 else private string[] defaultArgs = ["sudo", "docker"];
58 
59 private string runCommand(string[] args) @safe
60 {
61     string[] command = defaultArgs ~ args;
62     auto res = execute(command);
63     if (res.status != 0) throw new Exception("cannot run command: '" ~ command.join(" ") ~ "'. command exited with " ~ res.status.to!string);
64     return res.output;
65 }
66 
67 /++
68  + run docker in a shell
69  + Returns: ?
70  +/
71 public string runDockerShell(DockerOptions options) @safe
72 {
73     string[] dockerArgs = ["run", "-it"];
74 
75     if (options.remove) dockerArgs ~= ["--rm"];
76     if (options.detach) dockerArgs ~= ["--detach"];
77 
78     if (options.user != null) dockerArgs ~= ["--user", options.user];
79     if (options.name != null) dockerArgs ~= ["--name", options.name];
80 
81     if (options.privileged) dockerArgs ~= ["--privileged"];
82 
83     if (options.gdb) dockerArgs ~= ["--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined"];
84 
85     dockerArgs ~= [options.image];
86     
87     if (options.command != null) dockerArgs ~= options.command.split(" ");
88 
89     return runCommand(dockerArgs);
90 }
91 
92 /++
93  + lists all containers
94  + Returns: a list of objects of info of all the containers
95  +/
96 public DockerContainerInfo[] runDockerContainerList() @safe
97 {
98     import std.uni : isWhite;
99 
100     string[] dockerArgs = ["ps", "-a", "--format", "\"table {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}\""];
101 
102     string[] containers = runCommand(dockerArgs).split("\n");
103     DockerContainerInfo[] containerInfos = [];
104 
105     foreach (container; containers)
106     {
107         string[] containerParts = container.split!isWhite;
108 
109         if (containerParts.length < 5) continue;
110 
111         containerInfos ~= DockerContainerInfo(containerParts[1], containerParts[2], containerParts[3], containerParts[4]);
112     }
113 
114     return containerInfos;
115 }
116 
117 /++
118  + build a dockerfile
119  + Returns: ?
120  +/
121 public string runDockerBuild(string imageName, string dockerFile) @safe
122 {
123     string[] dockerArgs = ["build", "-f", dockerFile, "-t", imageName, "."];
124     return runCommand(dockerArgs);
125 }