Skip to content

Commit f8dfe42

Browse files
committed
feat(mcl): add compare_disko command
1 parent 2e14a60 commit f8dfe42

File tree

4 files changed

+161
-3
lines changed

4 files changed

+161
-3
lines changed

packages/mcl/src/main.d

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ alias supportedCommands = imported!`std.traits`.AliasSeq!(
1818
cmds.ci,
1919
cmds.machine,
2020
cmds.config,
21+
cmds.compare_disko,
2122
);
2223

2324
int main(string[] args)
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
module mcl.commands.compare_disko;
2+
3+
import mcl.utils.env : optional, parseEnv;
4+
import mcl.utils.nix : nix;
5+
import mcl.utils.path : getTopLevel;
6+
import mcl.utils.process : execute;
7+
8+
import std.typecons : Tuple, tuple;
9+
import std.file : mkdirRecurse, exists, rmdirRecurse;
10+
import std.format : fmt = format;
11+
import std.stdio;
12+
import std.json : JSONValue, parseJSON, JSONOptions;
13+
import std.file : write;
14+
import std.logger : tracef, errorf, infof;
15+
import std.process : environment;
16+
import mcl.utils.log : errorAndExit;
17+
18+
struct Params
19+
{
20+
@optional() string baseBranch;
21+
22+
void setup(){
23+
if (baseBranch == null){
24+
baseBranch = "main";
25+
}
26+
}
27+
}
28+
29+
struct MachineChanges{
30+
string machine;
31+
bool _config;
32+
bool _create;
33+
}
34+
35+
// TODO: handle case where a machine is missing from one branch
36+
export void compare_disko(string[] args){
37+
nix.eval!JSONValue("", [], "");
38+
const params = parseEnv!Params;
39+
JSONValue configurations = nix.flake!JSONValue("", [], "show");
40+
string[] machines;
41+
foreach (string k, JSONValue v; configurations["nixosConfigurations"]){
42+
if (k[$-3 .. $] != "-vm" &&
43+
k != "gitlab-runner-container" &&
44+
k != "minimal-container" )
45+
{
46+
machines ~= k;
47+
}
48+
}
49+
50+
string gitRoot = getTopLevel();
51+
string worktreeBaseBranch = gitRoot~"-"~params.baseBranch;
52+
53+
if (execute("git rev-parse --abbrev-ref HEAD") == params.baseBranch){
54+
errorAndExit("Trying to compare branch "~params.baseBranch~" with itself. Quitting.");
55+
}
56+
57+
execute(["git", "worktree" , "add", worktreeBaseBranch, params.baseBranch]);
58+
string freshTestsDir = gitRoot~"/disko-tests";
59+
string staleTestsDir = worktreeBaseBranch~"/disko-tests";
60+
mkdirRecurse(staleTestsDir);
61+
mkdirRecurse(freshTestsDir);
62+
63+
Tuple!(string , string )[] machineDiffs;
64+
MachineChanges[] machineChanges;
65+
66+
foreach (string m; machines){
67+
MachineChanges mc;
68+
mc.machine = m;
69+
foreach (setting; __traits(allMembers, MachineChanges)){
70+
static if (is(typeof(__traits(getMember, mc, setting)) == bool)){
71+
string new_setting =
72+
nix.eval(gitRoot~"#nixosConfigurations."~m~".config.disko.devices."~setting, [
73+
"--option", "warn-dirty", "false",
74+
"--accept-flake-config"]);
75+
tracef("CREATING %s_%s_new FILE", m, setting);
76+
write(freshTestsDir~"/"~m~"_"~setting~"_new", new_setting);
77+
string old_setting =
78+
nix.eval(worktreeBaseBranch~"#nixosConfigurations."~m~".config.disko.devices."~setting, [
79+
"--option", "warn-dirty", "false",
80+
"--accept-flake-config"]);
81+
tracef("CREATING %s_%s_old FILE", m, setting);
82+
write(staleTestsDir~"/"~m~"_"~setting~"_old", old_setting);
83+
84+
85+
string diff = execute([
86+
"git", "--no-pager", "diff", "--no-index",
87+
staleTestsDir~"/"~m~"_"~setting~"_old",
88+
freshTestsDir~"/"~m~"_"~setting~"_new"]);
89+
90+
if (diff == ""){
91+
infof("✔ NO DIFFERENCE IN %s", m);
92+
__traits(getMember, mc, setting) = true;
93+
}
94+
else{
95+
infof("✖ DIFFERENCE IN %s", m);
96+
__traits(getMember, mc, setting) = false;
97+
}
98+
}
99+
}
100+
machineChanges ~= mc;
101+
}
102+
infof("------------------------------------------------------");
103+
if(machineDiffs.length == 0){
104+
infof("✔✔✔ NO CONFIGS WITH DIFFS");
105+
}
106+
else{
107+
infof("✖✖✖ LIST OF CONFIGS WITH DIFFS");
108+
foreach(mc; machineChanges){
109+
infof(mc.machine);
110+
}
111+
}
112+
create_comment(machineChanges);
113+
114+
// Cleanup
115+
execute(["git", "worktree" , "remove", worktreeBaseBranch, "--force"]);
116+
rmdirRecurse(freshTestsDir);
117+
}
118+
119+
void create_comment(MachineChanges[] machineChanges){
120+
string data = "Thanks for your Pull Request!";
121+
if (machineChanges.length == 0){
122+
data ~="\n\n✅ There have been no changes to disko configs";
123+
}
124+
else{
125+
// TODO: Change the generation of the table to have as many collumns as fields in MachineChanges at compile time
126+
data ~= "\n\nBellow you will find a summary of machines and whether their disko attributes have differences.";
127+
128+
data ~= "\n\n";
129+
foreach (string field; __traits(allMembers, MachineChanges)){
130+
data~="| " ~ field ~ " ";
131+
}
132+
data ~= "|\n";
133+
foreach (string field; __traits(allMembers, MachineChanges)){
134+
data~="| --- ";
135+
}
136+
data ~= "|\n";
137+
138+
foreach(mc; machineChanges){
139+
foreach (string field; __traits(allMembers, MachineChanges)){
140+
static if (is(typeof(__traits(getMember, mc, field)) == bool)){
141+
data~="| " ~ (__traits(getMember, mc, field) ? "🟩" : "⚠️") ~ " ";
142+
}
143+
else static if (is(typeof(__traits(getMember, mc, field)) == string)){
144+
data~="| " ~ __traits(getMember, mc, field) ~ " ";
145+
}
146+
else{
147+
assert(0);
148+
}
149+
}
150+
data ~= "|\n";
151+
}
152+
}
153+
write("comment.md", data);
154+
}

packages/mcl/src/src/mcl/commands/package.d

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ public import mcl.commands.ci : ci;
88
public import mcl.commands.host_info : host_info;
99
public import mcl.commands.machine : machine;
1010
public import mcl.commands.config : config;
11+
public import mcl.commands.compare_disko: compare_disko;

packages/mcl/src/src/mcl/utils/nix.d

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ struct NixCommand
6161

6262
template opDispatch(string commandName)
6363
{
64-
T opDispatch(T = string)(string path, string[] args = [])
64+
T opDispatch(T = string)(string path, string[] args = [], string subcommand = "")
6565
{
6666
import std.algorithm : canFind;
6767

@@ -81,8 +81,10 @@ struct NixCommand
8181

8282
auto command = [
8383
"nix", "--experimental-features", "nix-command flakes",
84-
commandName,
85-
] ~ args ~ path;
84+
commandName
85+
] ~ (subcommand == "" ? [] : [ subcommand ]) ~ args ~ path;
86+
87+
writeln(command);
8688

8789
auto output = command.execute(true).strip();
8890

0 commit comments

Comments
 (0)