Skip to content

Commit b63be4f

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

File tree

4 files changed

+194
-3
lines changed

4 files changed

+194
-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: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
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+
import mcl.utils.log : errorAndExit;
8+
9+
import std.json : JSONValue;
10+
import std.file : write;
11+
import std.logger : infof;
12+
import std.algorithm : uniq;
13+
import std.array;
14+
import std.meta : Filter;
15+
16+
struct Params
17+
{
18+
@optional() string baseBranch;
19+
20+
void setup(){
21+
if (baseBranch == null){
22+
baseBranch = "main";
23+
}
24+
}
25+
}
26+
27+
enum ChangeStatus{
28+
Unchanged,
29+
Changed,
30+
Removed,
31+
New
32+
}
33+
34+
struct MachineChanges{
35+
string machine;
36+
ChangeStatus _config;
37+
ChangeStatus _create;
38+
}
39+
40+
enum string[] diskoOptions = [ __traits(allMembers, MachineChanges)[1 .. $] ];
41+
42+
string statusToSymbol(ChangeStatus s) {
43+
final switch(s){
44+
case ChangeStatus.Unchanged:
45+
return "🟩";
46+
break;
47+
case ChangeStatus.Changed:
48+
return "";
49+
break;
50+
case ChangeStatus.Removed:
51+
return "🗑";
52+
break;
53+
case ChangeStatus.New:
54+
return "🧩";
55+
break;
56+
}
57+
}
58+
59+
export void compare_disko(string[] args){
60+
const params = parseEnv!Params;
61+
62+
JSONValue configurations = nix.flake!JSONValue("", [], "show");
63+
64+
auto machinesNew = appender!(string[])();
65+
foreach (string k, JSONValue v; configurations["nixosConfigurations"]){
66+
if (k[$-3 .. $] != "-vm" &&
67+
k != "gitlab-runner-container" &&
68+
k != "minimal-container" )
69+
{
70+
machinesNew ~= k;
71+
}
72+
}
73+
74+
auto attr = constructCommandAttr("./.", machinesNew[]);
75+
auto machineOptionsRootNew = nix.eval!JSONValue("", ["--impure", "--expr", attr]);
76+
77+
78+
string gitRoot = getTopLevel();
79+
string worktreeBaseBranch = gitRoot~"-"~params.baseBranch;
80+
81+
if (execute("git rev-parse --abbrev-ref HEAD") == params.baseBranch){
82+
errorAndExit("Trying to compare branch "~params.baseBranch~" with itself. Quitting.");
83+
}
84+
85+
execute(["git", "worktree" , "add", worktreeBaseBranch, params.baseBranch]);
86+
87+
configurations = nix.flake!JSONValue(worktreeBaseBranch, [], "show");
88+
89+
auto machinesOld = appender!(string[])();
90+
foreach (string k, JSONValue v; configurations["nixosConfigurations"]){
91+
if (k[$-3 .. $] != "-vm" &&
92+
k != "gitlab-runner-container" &&
93+
k != "minimal-container" )
94+
{
95+
machinesOld ~= k;
96+
}
97+
}
98+
99+
attr = constructCommandAttr(worktreeBaseBranch, machinesOld[]);
100+
auto machineOptionsRootOld = nix.eval!JSONValue("", ["--impure", "--expr", attr]);
101+
102+
auto machineChanges = appender!(MachineChanges[])();
103+
104+
string[] machines = uniq(machinesOld[] ~ machinesNew[]).array;
105+
106+
foreach (string m; machines){
107+
MachineChanges mc;
108+
mc.machine = m;
109+
if (m in machineOptionsRootOld && !(m in machineOptionsRootNew)){
110+
static foreach (setting; diskoOptions){
111+
__traits(getMember, mc, setting) = ChangeStatus.Removed;
112+
}
113+
}
114+
else if (m in machineOptionsRootNew && !(m in machineOptionsRootOld)){
115+
static foreach (setting; diskoOptions){
116+
__traits(getMember, mc, setting) = ChangeStatus.New;
117+
}
118+
}
119+
else{
120+
static foreach (setting; diskoOptions){
121+
if (machineOptionsRootOld[m][setting] == machineOptionsRootNew[m][setting]){
122+
__traits(getMember, mc, setting) = ChangeStatus.Unchanged;
123+
}
124+
else{
125+
__traits(getMember, mc, setting) = ChangeStatus.Changed;
126+
}
127+
}
128+
}
129+
machineChanges ~= mc;
130+
}
131+
132+
create_comment(machineChanges[]);
133+
134+
execute(["git", "worktree" , "remove", worktreeBaseBranch, "--force"]);
135+
}
136+
137+
void create_comment(MachineChanges[] machineChanges){
138+
auto data = appender!string;
139+
data ~= "Thanks for your Pull Request!";
140+
if (machineChanges.length == 0){
141+
data ~="\n\n✅ There have been no changes to disko configs";
142+
}
143+
else{
144+
data ~= "\n\nBellow you will find a summary of machines and whether their disko attributes have differences.";
145+
data ~= "\n\n**Legend:**";
146+
data ~= "\n🟩 = No changes";
147+
data ~= "\n⚡ = Something is different";
148+
data ~= "\n🗑 = Has been removed";
149+
data ~= "\n🧩 = Has been added";
150+
151+
data ~= "\n\n";
152+
foreach (string field; __traits(allMembers, MachineChanges)){
153+
data~="| " ~ field ~ " ";
154+
}
155+
data ~= "|\n";
156+
foreach (string field; __traits(allMembers, MachineChanges)){
157+
data~="| --- ";
158+
}
159+
data ~= "|\n";
160+
161+
foreach(mc; machineChanges){
162+
foreach (string field; __traits(allMembers, MachineChanges)){
163+
static if (is(typeof(__traits(getMember, mc, field)) == string)){
164+
data~="| " ~ __traits(getMember, mc, field) ~ " ";
165+
}
166+
else {
167+
data~="| " ~ statusToSymbol(__traits(getMember, mc, field)) ~ " ";
168+
}
169+
}
170+
data ~= "|\n";
171+
}
172+
}
173+
write("comment.md", data[]);
174+
}
175+
176+
177+
string constructCommandAttr(string flakePath, string[] machines){
178+
auto ret = appender!string;
179+
ret ~= "let flake = (builtins.getFlake (builtins.toString " ~ flakePath ~ ")); in { ";
180+
foreach (m; machines){
181+
ret ~= m ~ " = { ";
182+
foreach (option; diskoOptions){
183+
ret ~= option ~ " = flake.nixosConfigurations." ~ m ~ ".config.disko.devices." ~ option ~ "; ";
184+
}
185+
ret ~= "}; ";
186+
}
187+
ret ~= "}";
188+
return ret[];
189+
}

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: 3 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

@@ -80,9 +80,9 @@ struct NixCommand
8080
args = ["--json"] ~ args;
8181

8282
auto command = [
83-
"nix", "--experimental-features", "nix-command flakes",
83+
"nix", "--experimental-features", "nix-command flakes pipe-operators",
8484
commandName,
85-
] ~ args ~ path;
85+
] ~ (subcommand == "" ? [] : [ subcommand ]) ~ args ~ path;
8686

8787
auto output = command.execute(true).strip();
8888

0 commit comments

Comments
 (0)