Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions .github/workflows/reusable-compare-disko.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: 'Compare Disko NixOS configs'

on:
# Allow this workflow to be reused by other workflows:
workflow_call:
inputs:
runner:
description: 'JSON-encoded list of runner labels'
default: '["self-hosted"]'
required: false
type: string
secrets:
CACHIX_AUTH_TOKEN:
description: 'Cachix auth token'
required: true

jobs:
post-initial-comment:
runs-on: ${{ fromJSON(inputs.runner) }}
steps:
- name: 'Post initial disko status comment'
uses: marocchino/[email protected]
with:
recreate: true
message: |
Thanks for your Pull Request!

This comment will be updated automatically with the status of disko on each machine.
compare-disko-configs:
runs-on: ${{ fromJSON(inputs.runner) }}
name: 'Compare disko configs on machines'

steps:
- uses: actions/checkout@v4

- name: Install Nix
uses: metacraft-labs/nixos-modules/.github/install-nix@main
with:
cachix-cache: ${{ vars.CACHIX_CACHE }}
cachix-auth-token: ${{ secrets.CACHIX_AUTH_TOKEN }}

- name: Compare disko configurations
env:
CACHIX_CACHE: ${{ vars.CACHIX_CACHE }}
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
# MCL_BRANCH: ${{ github.repository == 'metacraft-labs/nixos-modules' && github.sha || 'main' }}
MCL_BRANCH: ${{ github.repository == 'metacraft-labs/nixos-modules' && github.sha || 'feat/compare-disko' }}
run: nix run --accept-flake-config github:metacraft-labs/nixos-modules/${{ env.MCL_BRANCH }}#mcl compare_disko

create-comment:
runs-on: ${{ fromJSON(inputs.runner) }}
name: 'Create comment'
needs: [post-initial-comment, compare-disko-configs]

steps:
- name: Post Comment
uses: marocchino/[email protected]
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
recreate: true
path: comment.md
1 change: 1 addition & 0 deletions packages/mcl/src/main.d
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ alias supportedCommands = imported!`std.traits`.AliasSeq!(
cmds.ci,
cmds.machine,
cmds.config,
cmds.compare_disko,
);

int main(string[] args)
Expand Down
189 changes: 189 additions & 0 deletions packages/mcl/src/src/mcl/commands/compare_disko.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
module mcl.commands.compare_disko;

import mcl.utils.env : optional, parseEnv;
import mcl.utils.nix : nix;
import mcl.utils.path : getTopLevel;
import mcl.utils.process : execute;
import mcl.utils.log : errorAndExit;

import std.json : JSONValue;
import std.file : write;
import std.logger : infof;
import std.algorithm : uniq;
import std.array;
import std.meta : Filter;

struct Params
{
@optional() string baseBranch;

void setup(){
if (baseBranch == null){
baseBranch = "main";
}
}
}

enum ChangeStatus{
Unchanged,
Changed,
Removed,
New
}

struct MachineChanges{
string machine;
ChangeStatus _config;
ChangeStatus _create;
}

enum string[] diskoOptions = [ __traits(allMembers, MachineChanges)[1 .. $] ];

string statusToSymbol(ChangeStatus s) {
final switch(s){
case ChangeStatus.Unchanged:
return "🟩";
break;
case ChangeStatus.Changed:
return "⚡";
break;
case ChangeStatus.Removed:
return "🗑";
break;
case ChangeStatus.New:
return "🧩";
break;
}
}

export void compare_disko(string[] args){
const params = parseEnv!Params;

JSONValue configurations = nix.flake!JSONValue("", [], "show");

auto machinesNew = appender!(string[])();
foreach (string k, JSONValue v; configurations["nixosConfigurations"]){
if (k[$-3 .. $] != "-vm" &&
k != "gitlab-runner-container" &&
k != "minimal-container" )
{
machinesNew ~= k;
}
}

auto attr = constructCommandAttr("./.", machinesNew[]);
auto machineOptionsRootNew = nix.eval!JSONValue("", ["--impure", "--expr", attr]);


string gitRoot = getTopLevel();
string worktreeBaseBranch = gitRoot~"-"~params.baseBranch;

if (execute("git rev-parse --abbrev-ref HEAD") == params.baseBranch){
errorAndExit("Trying to compare branch "~params.baseBranch~" with itself. Quitting.");
}

execute(["git", "worktree" , "add", worktreeBaseBranch, params.baseBranch]);

configurations = nix.flake!JSONValue(worktreeBaseBranch, [], "show");

auto machinesOld = appender!(string[])();
foreach (string k, JSONValue v; configurations["nixosConfigurations"]){
if (k[$-3 .. $] != "-vm" &&
k != "gitlab-runner-container" &&
k != "minimal-container" )
{
machinesOld ~= k;
}
}

attr = constructCommandAttr(worktreeBaseBranch, machinesOld[]);
auto machineOptionsRootOld = nix.eval!JSONValue("", ["--impure", "--expr", attr]);

auto machineChanges = appender!(MachineChanges[])();

string[] machines = uniq(machinesOld[] ~ machinesNew[]).array;

foreach (string m; machines){
MachineChanges mc;
mc.machine = m;
if (m in machineOptionsRootOld && !(m in machineOptionsRootNew)){
static foreach (setting; diskoOptions){
__traits(getMember, mc, setting) = ChangeStatus.Removed;
}
}
else if (m in machineOptionsRootNew && !(m in machineOptionsRootOld)){
static foreach (setting; diskoOptions){
__traits(getMember, mc, setting) = ChangeStatus.New;
}
}
else{
static foreach (setting; diskoOptions){
if (machineOptionsRootOld[m][setting] == machineOptionsRootNew[m][setting]){
__traits(getMember, mc, setting) = ChangeStatus.Unchanged;
}
else{
__traits(getMember, mc, setting) = ChangeStatus.Changed;
}
}
}
machineChanges ~= mc;
}

create_comment(machineChanges[]);

execute(["git", "worktree" , "remove", worktreeBaseBranch, "--force"]);
}

void create_comment(MachineChanges[] machineChanges){
auto data = appender!string;
data ~= "Thanks for your Pull Request!";
if (machineChanges.length == 0){
data ~="\n\n✅ There have been no changes to disko configs";
}
else{
data ~= "\n\nBellow you will find a summary of machines and whether their disko attributes have differences.";
data ~= "\n\n**Legend:**";
data ~= "\n🟩 = No changes";
data ~= "\n⚡ = Something is different";
data ~= "\n🗑 = Has been removed";
data ~= "\n🧩 = Has been added";

data ~= "\n\n";
foreach (string field; __traits(allMembers, MachineChanges)){
data~="| " ~ field ~ " ";
}
data ~= "|\n";
foreach (string field; __traits(allMembers, MachineChanges)){
data~="| --- ";
}
data ~= "|\n";

foreach(mc; machineChanges){
foreach (string field; __traits(allMembers, MachineChanges)){
static if (is(typeof(__traits(getMember, mc, field)) == string)){
data~="| " ~ __traits(getMember, mc, field) ~ " ";
}
else {
data~="| " ~ statusToSymbol(__traits(getMember, mc, field)) ~ " ";
}
}
data ~= "|\n";
}
}
write("comment.md", data[]);
}


string constructCommandAttr(string flakePath, string[] machines){
auto ret = appender!string;
ret ~= "let flake = (builtins.getFlake (builtins.toString " ~ flakePath ~ ")); in { ";
foreach (m; machines){
ret ~= m ~ " = { ";
foreach (option; diskoOptions){
ret ~= option ~ " = flake.nixosConfigurations." ~ m ~ ".config.disko.devices." ~ option ~ "; ";
}
ret ~= "}; ";
}
ret ~= "}";
return ret[];
}
1 change: 1 addition & 0 deletions packages/mcl/src/src/mcl/commands/package.d
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ public import mcl.commands.ci : ci;
public import mcl.commands.host_info : host_info;
public import mcl.commands.machine : machine;
public import mcl.commands.config : config;
public import mcl.commands.compare_disko: compare_disko;
6 changes: 3 additions & 3 deletions packages/mcl/src/src/mcl/utils/nix.d
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ struct NixCommand

template opDispatch(string commandName)
{
T opDispatch(T = string)(string path, string[] args = [])
T opDispatch(T = string)(string path, string[] args = [], string subcommand = "")
{
import std.algorithm : canFind;

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

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

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

Expand Down
Loading