Skip to content

Commit 7486ea6

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

File tree

4 files changed

+193
-3
lines changed

4 files changed

+193
-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: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
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+
// TODO: handle case where a machine is missing from one branch
60+
export void compare_disko(string[] args){
61+
const params = parseEnv!Params;
62+
63+
JSONValue configurations = nix.flake!JSONValue("", [], "show");
64+
65+
string[] machinesNew;
66+
foreach (string k, JSONValue v; configurations["nixosConfigurations"]){
67+
if (k[$-3 .. $] != "-vm" &&
68+
k != "gitlab-runner-container" &&
69+
k != "minimal-container" )
70+
{
71+
machinesNew ~= k;
72+
}
73+
}
74+
75+
auto attr = constructCommandAttr("./.", machinesNew);
76+
auto machineOptionsRootNew = nix.eval!JSONValue("", ["--impure", "--expr", attr]);
77+
78+
79+
string gitRoot = getTopLevel();
80+
string worktreeBaseBranch = gitRoot~"-"~params.baseBranch;
81+
82+
if (execute("git rev-parse --abbrev-ref HEAD") == params.baseBranch){
83+
errorAndExit("Trying to compare branch "~params.baseBranch~" with itself. Quitting.");
84+
}
85+
86+
execute(["git", "worktree" , "add", worktreeBaseBranch, params.baseBranch]);
87+
88+
configurations = nix.flake!JSONValue(worktreeBaseBranch, [], "show");
89+
90+
string[] machinesOld;
91+
foreach (string k, JSONValue v; configurations["nixosConfigurations"]){
92+
if (k[$-3 .. $] != "-vm" &&
93+
k != "gitlab-runner-container" &&
94+
k != "minimal-container" )
95+
{
96+
machinesOld ~= k;
97+
}
98+
}
99+
100+
attr = constructCommandAttr(worktreeBaseBranch, machinesOld);
101+
auto machineOptionsRootOld = nix.eval!JSONValue("", ["--impure", "--expr", attr]);
102+
103+
MachineChanges[] machineChanges;
104+
105+
string[] machines = uniq(machinesOld ~ machinesNew).array;
106+
107+
foreach (string m; machines){
108+
MachineChanges mc;
109+
mc.machine = m;
110+
if (m in machineOptionsRootOld && !(m in machineOptionsRootNew)){
111+
static foreach (setting; diskoOptions){
112+
__traits(getMember, mc, setting) = ChangeStatus.Removed;
113+
}
114+
}
115+
else if (m in machineOptionsRootNew && !(m in machineOptionsRootOld)){
116+
static foreach (setting; diskoOptions){
117+
__traits(getMember, mc, setting) = ChangeStatus.New;
118+
}
119+
}
120+
else{
121+
static foreach (setting; diskoOptions){
122+
if (machineOptionsRootOld[m][setting] == machineOptionsRootNew[m][setting]){
123+
__traits(getMember, mc, setting) = ChangeStatus.Unchanged;
124+
}
125+
else{
126+
__traits(getMember, mc, setting) = ChangeStatus.Changed;
127+
}
128+
}
129+
}
130+
machineChanges ~= mc;
131+
}
132+
133+
create_comment(machineChanges);
134+
135+
execute(["git", "worktree" , "remove", worktreeBaseBranch, "--force"]);
136+
}
137+
138+
void create_comment(MachineChanges[] machineChanges){
139+
string 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+
string attr = "let flake = (builtins.getFlake (builtins.toString " ~ flakePath ~ ")); in { ";
179+
foreach (m; machines){
180+
attr ~= m ~ " = { ";
181+
foreach (option; diskoOptions){
182+
attr ~= option ~ " = flake.nixosConfigurations." ~ m ~ ".config.disko.devices." ~ option ~ "; ";
183+
}
184+
attr ~= "}; ";
185+
}
186+
attr ~= "}";
187+
return attr;
188+
}

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)