diff --git a/.github/print-matrix/action.yml b/.github/print-matrix/action.yml index 8fb486e9..46ccd458 100644 --- a/.github/print-matrix/action.yml +++ b/.github/print-matrix/action.yml @@ -58,7 +58,7 @@ runs: CACHIX_AUTH_TOKEN: ${{ inputs.cachix-auth-token }} PRECALC_MATRIX: ${{ inputs.precalc_matrix }} MCL_BRANCH: ${{ github.repository == 'metacraft-labs/nixos-modules' && github.sha || 'main' }} - run: nix run --accept-flake-config github:metacraft-labs/nixos-modules/${{ env.MCL_BRANCH }}#mcl print_table + run: nix run --accept-flake-config github:metacraft-labs/nixos-modules/${{ env.MCL_BRANCH }}#mcl print-table - name: Update GitHub Comment uses: marocchino/sticky-pull-request-comment@v2.9.0 diff --git a/.github/workflows/reusable-flake-checks-ci-matrix.yml b/.github/workflows/reusable-flake-checks-ci-matrix.yml index 54ce16f2..f9505867 100644 --- a/.github/workflows/reusable-flake-checks-ci-matrix.yml +++ b/.github/workflows/reusable-flake-checks-ci-matrix.yml @@ -59,7 +59,7 @@ jobs: CACHIX_CACHE: ${{ vars.CACHIX_CACHE }} CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }} MCL_BRANCH: ${{ github.repository == 'metacraft-labs/nixos-modules' && github.sha || 'main' }} - run: nix run --accept-flake-config github:metacraft-labs/nixos-modules/${{ env.MCL_BRANCH }}#mcl shard_matrix + run: nix run --accept-flake-config github:metacraft-labs/nixos-modules/${{ env.MCL_BRANCH }}#mcl shard-matrix outputs: gen_matrix: ${{ steps.generate-matrix.outputs.gen_matrix }} @@ -91,7 +91,7 @@ jobs: FLAKE_PRE: ${{ matrix.prefix }} FLAKE_POST: ${{ matrix.postfix }} MCL_BRANCH: ${{ github.repository == 'metacraft-labs/nixos-modules' && github.sha || 'main' }} - run: nix run --accept-flake-config github:metacraft-labs/nixos-modules/${{ env.MCL_BRANCH }}#mcl ci_matrix + run: nix run --accept-flake-config github:metacraft-labs/nixos-modules/${{ env.MCL_BRANCH }}#mcl ci-matrix - uses: actions/upload-artifact@v4 with: @@ -222,4 +222,4 @@ jobs: CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }} CACHIX_ACTIVATE_TOKEN: '${{ secrets.CACHIX_ACTIVATE_TOKEN }}' MCL_BRANCH: ${{ github.repository == 'metacraft-labs/nixos-modules' && github.sha || 'main' }} - run: nix run --accept-flake-config github:metacraft-labs/nixos-modules/${{ env.MCL_BRANCH }}#mcl deploy_spec + run: nix run --accept-flake-config github:metacraft-labs/nixos-modules/${{ env.MCL_BRANCH }}#mcl deploy-spec diff --git a/packages/mcl/dub.sdl b/packages/mcl/dub.sdl index 011a343f..ea896f42 100644 --- a/packages/mcl/dub.sdl +++ b/packages/mcl/dub.sdl @@ -14,8 +14,6 @@ buildType "unittest-debug" { buildOptions "unittests" "debugMode" "debugInfo" } -dflags "-preview=in" -dflags "-preview=shortenedMethods" dflags "-defaultlib=libphobos2.so" platform="dmd" lflags "-fuse-ld=gold" platform="dmd" dflags "-mcpu=generic" platform="ldc" @@ -23,3 +21,4 @@ dflags "-mcpu=baseline" platform="dmd" dependency "mir-cpuid" version="~>1.2.11" dependency "silly" version="~>1.1.1" +dependency "argparse" version="~>1.4.1" diff --git a/packages/mcl/dub.selections.json b/packages/mcl/dub.selections.json index 2977f10a..a3f31e8e 100644 --- a/packages/mcl/dub.selections.json +++ b/packages/mcl/dub.selections.json @@ -1,6 +1,7 @@ { "fileVersion": 1, "versions": { + "argparse": "1.4.1", "mir-core": "1.7.1", "mir-cpuid": "1.2.11", "silly": "1.1.1" diff --git a/packages/mcl/src/main.d b/packages/mcl/src/main.d index 4412d7ea..bb93e89c 100644 --- a/packages/mcl/src/main.d +++ b/packages/mcl/src/main.d @@ -1,73 +1,89 @@ +module mcl.main; + import std.stdio : writefln, writeln, stderr; import std.array : replace; import std.getopt : getopt; import std.logger : infof, errorf, LogLevel; - +import std.sumtype : SumType, match; +import std.string : stripRight, stripLeft; +import std.algorithm : endsWith; +import std.format : format; import mcl.utils.path : rootDir; import mcl.utils.tui : bold; -import cmds = mcl.commands; +import mcl.commands; -alias supportedCommands = imported!`std.traits`.AliasSeq!( - cmds.get_fstab, - cmds.deploy_spec, - cmds.ci_matrix, - cmds.print_table, - cmds.shard_matrix, - cmds.host_info, - cmds.ci, - cmds.machine, - cmds.config, -); +import argparse; -int main(string[] args) -{ - if (args.length < 2) - return wrongUsage("no command selected"); - string cmd = args[1]; - LogLevel logLevel = LogLevel.info; - args.getopt("log-level", &logLevel); +@(Command(" ").Description(" ")) +struct unknown_command_args {} +int unknown_command(unknown_command_args unused) +{ + stderr.writeln("Unknown command. Use --help for a list of available commands."); + return 1; +} - setLogLevel(logLevel); +template genSubCommandArgs() +{ + const char[] genSubCommandArgs = + "@SubCommands\n"~ + "SumType!("~ + "get_fstab_args,"~ + "deploy_spec_args,"~ + "host_info_args,"~ + "config_args,"~ + "machine_args,"~ + "ci_matrix_args,"~ + "ci_args,"~ + "print_table_args,"~ + "shard_matrix_args,"~ + "Default!unknown_command_args"~ + ") cmd;"; +} - infof("Git root: '%s'", rootDir.bold); +template genSubCommandMatch() +{ + const char[] generateMatchString = () { + alias CmdTypes = typeof(MCLArgs.cmd).Types; + string match = "int result = args.cmd.match!("; - try switch (cmd) - { - default: - return wrongUsage("unknown command: `" ~ cmd ~ "`"); + static foreach (CmdType; CmdTypes) + {{ + string name = CmdType.stringof.replace("Default!(", "").stripRight(")"); + match ~= format("\n\t(%s a) => %s(a)", name, name.replace("_args", "")) ~ ", "; + }} + match = match.stripRight(", "); + match ~= "\n);"; - static foreach (command; supportedCommands) - case __traits(identifier, command): - { + return match; + }(); +} - infof("Running %s task", cmd.bold); - command(args[2..$]); - infof("Execution Succesfull"); - return 0; - } - } - catch (Exception e) - { - errorf("Task %s failed. Error:\n%s", cmd.bold, e); - return 1; - } +struct MCLArgs +{ + @NamedArgument(["log-level"]) + LogLevel logLevel = cast(LogLevel)-1; + mixin(genSubCommandArgs!()); } +mixin CLI!MCLArgs.main!((args) +{ + static assert(is(typeof(args) == MCLArgs)); + + LogLevel logLevel = LogLevel.info; + if (args.logLevel != cast(LogLevel)-1) + logLevel = args.logLevel; + setLogLevel(logLevel); + + mixin genSubCommandMatch; + + return 0; +}); + void setLogLevel(LogLevel l) { import std.logger : globalLogLevel, sharedLog; globalLogLevel = l; (cast()sharedLog()).logLevel = l; } - -int wrongUsage(string error) -{ - writefln("Error: %s.", error); - writeln("Usage:\n"); - static foreach (cmd; supportedCommands) - writefln(" mcl %s", __traits(identifier, cmd)); - - return 1; -} diff --git a/packages/mcl/src/src/mcl/commands/ci.d b/packages/mcl/src/src/mcl/commands/ci.d index 61d12588..c24154b1 100644 --- a/packages/mcl/src/src/mcl/commands/ci.d +++ b/packages/mcl/src/src/mcl/commands/ci.d @@ -8,6 +8,8 @@ import std.array : array, join; import std.conv : to; import std.process : ProcessPipes; +import argparse; + import mcl.utils.env : optional, parseEnv; import mcl.commands.ci_matrix: nixEvalJobs, SupportedSystem, Params, flakeAttr; import mcl.commands.shard_matrix: generateShardMatrix; @@ -18,6 +20,11 @@ import mcl.utils.json : toJSON; Params params; +@(Command("ci").Description("Run CI")) +struct ci_args +{ +} + export void ci(string[] args) { params = parseEnv!Params; diff --git a/packages/mcl/src/src/mcl/commands/ci_matrix.d b/packages/mcl/src/src/mcl/commands/ci_matrix.d index b8984b37..b4168690 100755 --- a/packages/mcl/src/src/mcl/commands/ci_matrix.d +++ b/packages/mcl/src/src/mcl/commands/ci_matrix.d @@ -16,6 +16,8 @@ import std.exception : enforce; import std.format : fmt = format; import std.logger : tracef, infof, errorf, warningf; +import argparse; + import mcl.utils.env : optional, MissingEnvVarsException, parseEnv; import mcl.utils.string : enumToString, StringRepresentation, MaxWidth, writeRecordAsTable; import mcl.utils.json : toJSON; @@ -141,16 +143,15 @@ version (unittest) ]; } -immutable Params params; +Params params; -version (unittest) {} else -shared static this() +@(Command("ci-matrix", "ci_matrix").Description("Print a table of the cache status of each package")) +struct ci_matrix_args { - params = parseEnv!Params; } - -export void ci_matrix(string[] args) +export void ci_matrix(ci_matrix_args args) { + params = parseEnv!Params; createResultDirs(); nixEvalForAllSystems().array.printTableForCacheStatus(); } @@ -186,13 +187,20 @@ Package[] checkCacheStatus(Package[] packages) return packages; } -export void print_table(string[] args) +@(Command("print-table", "print_table").Description("Print a table of the cache status of each package")) +struct print_table_args +{ +} + +export int print_table(print_table_args args) { createResultDirs(); getPrecalcMatrix() .checkCacheStatus() .printTableForCacheStatus(); + + return 0; } struct Params @@ -203,6 +211,7 @@ struct Params @optional() int maxWorkers; @optional() int maxMemory; @optional() bool isInitial; + string cachixCache; string cachixAuthToken; diff --git a/packages/mcl/src/src/mcl/commands/config.d b/packages/mcl/src/src/mcl/commands/config.d index 4ead6f48..4623c14f 100755 --- a/packages/mcl/src/src/mcl/commands/config.d +++ b/packages/mcl/src/src/mcl/commands/config.d @@ -6,6 +6,9 @@ import std.process : ProcessPipes, Redirect, wait, environment; import std.range : drop, front; import std.stdio : writeln; import std.string : indexOf; +import std.sumtype : SumType, match; + +import argparse; import mcl.utils.env : optional, parseEnv; import mcl.utils.fetch : fetchJson; @@ -14,112 +17,166 @@ import mcl.utils.nix : nix, queryStorePath; import mcl.utils.process : execute; import mcl.utils.string : camelCaseToCapitalCase; -export void config(string[] args) { - if (args.length == 0) { - errorAndExit("Usage: mcl config [args]"); - } - if (!checkRepo()) { - errorAndExit("This command must be run from a repository containing a NixOS machine configuration"); - } +@(Command("config").Description("Manage NixOS machine configurations")) +export struct config_args +{ + @SubCommands SumType!( + sys_args, + home_args, + start_vm_args, + Default!unknown_command_args + ) cmd; +} + +@(Command("sys").Description("Manage system configurations")) +struct sys_args +{ + @SubCommands SumType!( + sys_apply_args, + sys_edit_args, + Default!unknown_command_args + ) cmd; +} + +@(Command("apply").Description("Apply system configuration")) +struct sys_apply_args +{ + @(PositionalArgument(0).Placeholder("machine-name").Description("Name of the machine")) + string machineName; +} + +@(Command("edit").Description("Edit system configuration")) +struct sys_edit_args +{ + @(PositionalArgument(0).Placeholder("machine-name").Description("Name of the machine")) + string machineName; +} + +@(Command("home").Description("Manage home configurations")) +struct home_args +{ + @SubCommands SumType!( + home_apply_args, + home_edit_args, + Default!unknown_command_args + ) cmd; +} + +@(Command("apply").Description("Apply user configuration")) +struct home_apply_args +{ + @(PositionalArgument(0).Placeholder("desktop/server").Description("Type of home configuration")) + string type; +} + +@(Command("edit").Description("Edit user configuration")) +struct home_edit_args +{ + @(PositionalArgument(0).Placeholder("desktop/server").Description("Type of home configuration")) + string type; +} + +@(Command("start-vm").Description("Start a VM")) +struct start_vm_args +{ + @(PositionalArgument(0).Optional().Placeholder("vm-name").Description("Name of the VM")) + string vmName = ""; +} + +@(Command(" ").Description(" ")) +struct unknown_command_args +{ +} + +int unknown_command(unknown_command_args unused) +{ + errorAndExit("Unknown command. Use --help for a list of available commands."); + return 1; +} - string subcommand = args.front; - - switch (subcommand) { - case "sys": - sys(args.drop(1)); - break; - case "home": - home(args.drop(1)); - break; - case "start-vm": - startVM(args.drop(1)); - break; - default: - errorAndExit("Unknown config subcommand " ~ subcommand ~ ". Supported subcommands: sys, home, start-vm"); - break; +export int config(config_args args) +{ + if (!checkRepo()) + { + errorAndExit( + "This command must be run from a repository containing a NixOS machine configuration"); } + + return args.cmd.match!( + (sys_args a) => sys(a), + (home_args a) => home(a), + (start_vm_args a) => startVM(a.vmName), + (unknown_command_args a) => unknown_command(a) + ); } bool checkRepo() { const string[] validRepos = ["nixos-machine-config", "infra-lido"]; - string remoteOriginUrl = execute(["git", "config", "--get", "remote.origin.url"], false); + string remoteOriginUrl = execute([ + "git", "config", "--get", "remote.origin.url" + ], false); - foreach (string repo; validRepos) { - if (remoteOriginUrl.indexOf(repo) != -1) { + foreach (string repo; validRepos) + { + if (remoteOriginUrl.indexOf(repo) != -1) + { return true; } } return false; } -void executeCommand(string command) { +int executeCommand(string command) +{ auto exec = execute!ProcessPipes(command, true, false, Redirect.stderrToStdout); - wait(exec.pid); + return wait(exec.pid); } -void edit(string type, string path) { +int edit(string type, string path) +{ string editor = environment.get("EDITOR", "vim"); string user = environment.get("USER", "root"); writeln("Editing " ~ path ~ " configuration from: ", path); - final switch (type) { - case "system": - executeCommand(editor ~ " machines/*/" ~ path ~ "/*.nix"); - break; - case "user": - executeCommand(editor~ " users/" ~ user ~ "/gitconfig " ~ "users/" ~ user ~ "/*.nix " ~ "users/" ~ user ~ "/home-"~path~"/*.nix"); - break; + final switch (type) + { + case "system": + return executeCommand(editor ~ " machines/*/" ~ path ~ "/*.nix"); + case "user": + return executeCommand(editor ~ " users/" ~ user ~ "/gitconfig " ~ "users/" ~ user ~ "/*.nix " ~ "users/" ~ user ~ "/home-" ~ path ~ "/*.nix"); } } -void sys(string[] args) +int apply(string type, string value) { - if ((args.length < 1 || args.length > 2) && !["apply", "edit"].canFind(args.front)) - { - errorAndExit("Usage: mcl config sys apply or mcl config sys apply \n"~ - " mcl config sys edit or mcl config sys edit "); - } + writeln("Applying ", type, " configuration from: ", value); + return executeCommand("just switch-" ~ type ~ " " ~ value); +} - string machineName = args.length > 1 ? args[1] : ""; - final switch (args.front) { - case "apply": - writeln("Applying system configuration from: ", machineName); - executeCommand("just switch-system " ~ machineName); - break; - case "edit": - edit("system", machineName); - break; - } +int sys(sys_args args) +{ + return args.cmd.match!( + (sys_apply_args a) => apply("system", a.machineName), + (sys_edit_args a) => edit("system", a.machineName), + (unknown_command_args a) => unknown_command(a) + ); } -void home(string[] args) +int home(home_args args) { - if ((args.length != 2) && args.front != "apply") - { - errorAndExit("Usage: mcl config home apply \n"~ - " mcl config home edit "); - } - auto type = args[1]; - final switch (args.front) { - case "apply": - writeln("Applying home configuration from: ", type); - executeCommand("just switch-home " ~ type); - break; - case "edit": - edit("user", type); - break; - } + return args.cmd.match!( + (home_apply_args a) { + writeln("Applying home configuration from: ", a.type); + return executeCommand("just switch-home " ~ a.type); + }, + (home_edit_args a) => "user".edit(a.type), + (unknown_command_args a) => unknown_command(a) + ); } -void startVM(string[] args) +int startVM(string vmName) { - if (args.length != 1) - { - errorAndExit("Usage: mcl config start-vm "); - } - - string vmName = args.front; writeln("Starting VM: ", vmName); - executeCommand("just start-vm " ~ vmName); + return executeCommand("just start-vm " ~ vmName); } diff --git a/packages/mcl/src/src/mcl/commands/deploy_spec.d b/packages/mcl/src/src/mcl/commands/deploy_spec.d index 819fd4a2..593c7d00 100644 --- a/packages/mcl/src/src/mcl/commands/deploy_spec.d +++ b/packages/mcl/src/src/mcl/commands/deploy_spec.d @@ -5,6 +5,8 @@ import std.logger : infof, warningf; import std.file : exists; import std.path : buildPath; +import argparse; + import mcl.utils.process : spawnProcessInline; import mcl.utils.path : resultDir; import mcl.utils.cachix : cachixNixStoreUrl, DeploySpec, createMachineDeploySpec; @@ -13,7 +15,12 @@ import mcl.utils.json : tryDeserializeFromJsonFile, writeJsonFile; import mcl.commands.ci_matrix : flakeAttr, params, nixEvalJobs, SupportedSystem; -export void deploy_spec(string[] args) + +@(Command("deploy-spec", "deploy_spec").Description("Evaluate the Nixos machine configurations in bareMetalMachines and deploy them to cachix.")) +export struct deploy_spec_args { +} + +export int deploy_spec(deploy_spec_args args) { const deploySpecFile = resultDir.buildPath("cachix-deploy-spec.json"); @@ -51,9 +58,11 @@ export void deploy_spec(string[] args) infof("%s machines will be deployed.", spec.agents.length); if (!spec.agents.length) - return; + return 1; spawnProcessInline([ "cachix", "deploy", "activate", deploySpecFile, "--async" ]); + + return 0; } diff --git a/packages/mcl/src/src/mcl/commands/get_fstab.d b/packages/mcl/src/src/mcl/commands/get_fstab.d index d063b7ed..72bbb579 100755 --- a/packages/mcl/src/src/mcl/commands/get_fstab.d +++ b/packages/mcl/src/src/mcl/commands/get_fstab.d @@ -6,6 +6,8 @@ import std.json : JSONValue; import std.format : fmt = format; import std.exception : enforce; +import argparse; + import mcl.utils.cachix : cachixNixStoreUrl, getCachixDeploymentApiUrl; import mcl.utils.env : optional, parseEnv; import mcl.utils.fetch : fetchJson; @@ -13,40 +15,45 @@ import mcl.utils.nix : queryStorePath, nix; import mcl.utils.string : camelCaseToCapitalCase; import mcl.utils.process : execute; -export void get_fstab(string[] args) +export int get_fstab(get_fstab_args args) { - const params = parseEnv!Params; - const machineStorePath = getCachixDeploymentStorePath(params); + args.cachixStoreUrl = cachixNixStoreUrl(args.cachixCache); + if (!args.cachixDeployWorkspace) + args.cachixDeployWorkspace = args.cachixCache; + + const machineStorePath = getCachixDeploymentStorePath(args); const fstabStorePath = queryStorePath( machineStorePath, ["-etc", "-etc-fstab"], - params.cachixStoreUrl + args.cachixStoreUrl ); nix.build(fstabStorePath); writeln(fstabStorePath); + return 0; } -struct Params -{ +@(Command("get-fstab", "get_fstab").Description("Get the store path of the fstab file for a deployment")) +export struct get_fstab_args { + @(NamedArgument(["cachix-auth-token"]).Required().Placeholder("XXX").Description("Auth Token for Cachix")) string cachixAuthToken; + @(NamedArgument(["cachix-cache"]).Required().Placeholder("cache").Description("Which Cachix cache to use")) string cachixCache; - @optional() string cachixStoreUrl; - @optional() string cachixDeployWorkspace; - string machineName; - uint deploymentId; - void setup() - { + @(NamedArgument(["cachix-store-url"]).Placeholder("https://...").Description("URL of the Cachix store")) + string cachixStoreUrl = ""; + @(NamedArgument(["cachix-deploy-workspace"]).Placeholder("agent-workspace").Description("Cachix workspace to deploy to")) + string cachixDeployWorkspace = ""; - cachixStoreUrl = cachixNixStoreUrl(cachixCache); - if (!cachixDeployWorkspace) - cachixDeployWorkspace = cachixCache; - } + @(PositionalArgument(0).Placeholder("machine-name").Description("Name of the machine")) + string machineName; + @(PositionalArgument(1).Placeholder("deployment-id").Description("ID of the deployment")) + uint deploymentId; } -string getCachixDeploymentStorePath(Params p) + +string getCachixDeploymentStorePath(get_fstab_args args) { - const url = getCachixDeploymentApiUrl(p.cachixDeployWorkspace, p.machineName, p.deploymentId); - const response = fetchJson(url, p.cachixAuthToken); + const url = getCachixDeploymentApiUrl(args.cachixDeployWorkspace, args.machineName, args.deploymentId); + const response = fetchJson(url, args.cachixAuthToken); return response["storePath"].get!string; } diff --git a/packages/mcl/src/src/mcl/commands/host_info.d b/packages/mcl/src/src/mcl/commands/host_info.d index 464a6575..cdc21925 100644 --- a/packages/mcl/src/src/mcl/commands/host_info.d +++ b/packages/mcl/src/src/mcl/commands/host_info.d @@ -19,6 +19,8 @@ import std.format : format; import std.system : nativeEndian = endian;; import core.stdc.string : strlen; +import argparse; + import mcl.utils.env : parseEnv, optional; import mcl.utils.json : toJSON; import mcl.utils.process : execute, isRoot; @@ -30,9 +32,7 @@ import mcl.utils.coda : CodaApiClient, RowValues, CodaCell; struct Params { @optional() string codaApiToken; - void setup() - { - } + void setup() {} } string[string] cpuinfo; @@ -59,7 +59,10 @@ string[string] getProcInfo(string fileOrData, bool file = true) return r; } -export void host_info(string[] args) +@(Command("host-info", "host_info").Description("Get information about the host machine")) +struct host_info_args {} + +export int host_info(host_info_args args) { const Params params = parseEnv!Params; @@ -72,13 +75,15 @@ export void host_info(string[] args) if (!params.codaApiToken) { writeln("No Coda API token specified -> not uploading"); - return; + return 1; } writeln("Coda API token specified -> uploading"); auto coda = CodaApiClient(params.codaApiToken); coda.uploadHostInfo(hostInfo); + return 1; + } Info gatherHostInfo() diff --git a/packages/mcl/src/src/mcl/commands/machine.d b/packages/mcl/src/src/mcl/commands/machine.d index 008d0a86..65a0d228 100755 --- a/packages/mcl/src/src/mcl/commands/machine.d +++ b/packages/mcl/src/src/mcl/commands/machine.d @@ -1,6 +1,9 @@ module mcl.commands.machine; import std; + +import argparse; + import mcl.utils.log : prompt; import mcl.utils.process : execute; import mcl.utils.nix : nix, toNix, Literal, mkDefault; @@ -173,22 +176,22 @@ string[] getGroups() return groups; } -User createUser() { +User createUser(create_machine_args args) { - auto createUser = params.createUser || prompt!bool("Create new user"); + auto createUser = args.createUser || prompt!bool("Create new user"); if (!createUser) { string[] existingUsers = getExistingUsers(); - string userName = params.userName != "" ? params.userName : prompt!string("Select an existing username", existingUsers); + string userName = args.userName != "" ? args.userName : prompt!string("Select an existing username", existingUsers); return getUser(userName); } else { User user; - user.userName = params.userName != "" ? params.userName : prompt!string("Enter the new username"); - user.userInfo.description = params.description != "" ? params.description : prompt!string("Enter the user's description/full name"); - user.userInfo.isNormalUser = params.isNormalUser || prompt!bool("Is this a normal or root user"); - user.userInfo.extraGroups = (params.extraGroups != "" ? params.extraGroups : prompt!string("Enter the user's extra groups (comma delimited)", getGroups())).split(",").map!(strip).array; + user.userName = args.userName != "" ? args.userName : prompt!string("Enter the new username"); + user.userInfo.description = args.description != "" ? args.description : prompt!string("Enter the user's description/full name"); + user.userInfo.isNormalUser = args.isNormalUser || prompt!bool("Is this a normal or root user"); + user.userInfo.extraGroups = (args.extraGroups != "" ? args.extraGroups : prompt!string("Enter the user's extra groups (comma delimited)", getGroups())).split(",").map!(strip).array; createUserDir(user); return user; } @@ -220,8 +223,8 @@ struct MachineConfiguration MachineUserInfo users; } -void createMachine(MachineType machineType, string machineName, User user) { - auto infoJSON = execute(["ssh", params.sshPath, "sudo nix --experimental-features \\'nix-command flakes\\' --refresh --accept-flake-config run github:metacraft-labs/nixos-modules/#mcl host_info"],false, false); +void createMachine(create_machine_args args, MachineType machineType, string machineName, User user) { + auto infoJSON = execute(["ssh", args.sshPath, "sudo nix --experimental-features \\'nix-command flakes\\' --refresh --accept-flake-config run github:metacraft-labs/nixos-modules/#mcl host-info"],false, false); auto infoJSONParsed = infoJSON.parseJSON; Info info = infoJSONParsed.fromJSON!Info; @@ -273,7 +276,7 @@ void createMachine(MachineType machineType, string machineName, User user) { // Disks hardwareConfiguration.disko.DISKO.makeZfsPartitions.swapSizeGB = (info.hardwareInfo.memoryInfo.totalGB.to!double*1.5).to!int; auto nvmeDevices = info.hardwareInfo.storageInfo.devices.filter!(a => a.dev.indexOf("nvme") != -1 || a.model.indexOf("SSD") != -1).array.map!(a => a.model.replace(" ", "_") ~ "_" ~ a.serial).array; - string[] disks = (nvmeDevices.length == 1 ? nvmeDevices[0] : (params.disks != "" ? params.disks : prompt!string("Enter the disks to use (comma delimited)", nvmeDevices))).split(",").map!(strip).array.map!(a => "/dev/disk/by-id/nvme-" ~ a).array; + string[] disks = (nvmeDevices.length == 1 ? nvmeDevices[0] : (args.disks != "" ? args.disks : prompt!string("Enter the disks to use (comma delimited)", nvmeDevices))).split(",").map!(strip).array.map!(a => "/dev/disk/by-id/nvme-" ~ a).array; hardwareConfiguration.disko.DISKO.makeZfsPartitions.disks = disks; hardwareConfiguration = hardwareConfiguration.uniqArrays; @@ -365,43 +368,58 @@ struct HardwareConfiguration { Services services; } -void createMachineConfiguration() +int createMachineConfiguration(create_machine_args args) { checkifNixosMachineConfigRepo(); - auto machineType = cast(int)params.machineType != 0 ? params.machineType : prompt!MachineType("Machine type"); - auto machineName = params.machineName != "" ? params.machineName : prompt!string("Enter the name of the machine"); + auto machineType = cast(int)args.machineType != 0 ? args.machineType : prompt!MachineType("Machine type"); + auto machineName = args.machineName != "" ? args.machineName : prompt!string("Enter the name of the machine"); User user; - user = createUser(); - machineType.createMachine( machineName, user); + user = createUser(args); + args.createMachine(machineType, machineName, user); + return 0; } -Params params; -export void machine(string[] args) +export int machine(machine_args args) { - params = parseEnv!Params; - switch (args.front) - { - case "create": - createMachineConfiguration(); - break; - default: - assert(0, "Unknown machine action: " ~ args.front); - } + return args.cmd.match!( + (create_machine_args a) => createMachineConfiguration(a), + (unknown_command_args a) => unknown_command(a) + ); } -struct Params -{ +@(Command("create").Description("Create a new machine")) +struct create_machine_args { + + @(PositionalArgument(0).Placeholder("ssh").Description("SSH path to the machine")) string sshPath; - @optional() bool createUser; - @optional() string userName; - @optional() string machineName; - @optional() string description; - @optional() bool isNormalUser; - @optional() string extraGroups; - @optional() MachineType machineType = cast(MachineType)0; - @optional() string disks; - - void setup() - { - } + @(NamedArgument(["create-user"]).Placeholder("true/false").Description("Create a new user")) + bool createUser; + @(NamedArgument(["user-name"]).Placeholder("username").Description("Username")) + string userName; + @(NamedArgument(["machine-name"]).Placeholder("machine-name").Description("Name of the machine")) + string machineName; + @(NamedArgument(["description"]).Placeholder("description").Description("Description of the user")) + string description; + @(NamedArgument(["is-normal-user"]).Placeholder("true/false").Description("Is this a normal user")) + bool isNormalUser; + @(NamedArgument(["extra-groups"]).Placeholder("group1,group2").Description("Extra groups for the user")) + string extraGroups; + @(NamedArgument(["machine-type"]).Placeholder("desktop/server/container").Description("Type of machine")) + MachineType machineType = cast(MachineType)0; + @(NamedArgument(["disks"]).Placeholder("CT2000P3PSSD8_2402E88C1519,...").Description("Disks to use")) + string disks; +} +@(Command(" ").Description(" ")) +struct unknown_command_args {} +int unknown_command(unknown_command_args unused) +{ + stderr.writeln("Unknown machine command. Use --help for a list of available commands."); + return 1; +} + +@(Command("machine").Description("Manage machines")) +struct machine_args +{ + + @SubCommands SumType!(create_machine_args,Default!unknown_command_args) cmd; } diff --git a/packages/mcl/src/src/mcl/commands/package.d b/packages/mcl/src/src/mcl/commands/package.d index 4ab52cce..0deef952 100644 --- a/packages/mcl/src/src/mcl/commands/package.d +++ b/packages/mcl/src/src/mcl/commands/package.d @@ -1,10 +1,10 @@ module mcl.commands; -public import mcl.commands.get_fstab : get_fstab; -public import mcl.commands.deploy_spec : deploy_spec; -public import mcl.commands.ci_matrix : ci_matrix, print_table; -public import mcl.commands.shard_matrix : shard_matrix; -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.get_fstab : get_fstab, get_fstab_args; +public import mcl.commands.deploy_spec : deploy_spec, deploy_spec_args; +public import mcl.commands.ci_matrix : ci_matrix,ci_matrix_args, print_table,print_table_args; +public import mcl.commands.shard_matrix : shard_matrix, shard_matrix_args; +public import mcl.commands.ci : ci, ci_args; +public import mcl.commands.host_info : host_info, host_info_args; +public import mcl.commands.machine : machine, machine_args; +public import mcl.commands.config : config, config_args; diff --git a/packages/mcl/src/src/mcl/commands/shard_matrix.d b/packages/mcl/src/src/mcl/commands/shard_matrix.d index b4307844..3239e483 100644 --- a/packages/mcl/src/src/mcl/commands/shard_matrix.d +++ b/packages/mcl/src/src/mcl/commands/shard_matrix.d @@ -13,26 +13,26 @@ import std.regex : matchFirst, regex; import std.stdio : writeln; import std.string : strip; +import argparse; + import mcl.utils.env : parseEnv, optional; import mcl.utils.json : toJSON; import mcl.utils.nix : nix; import mcl.utils.path : createResultDirs, resultDir, rootDir; -export void shard_matrix(string[] args) +@(Command("shard-matrix", "shard_matrix").Description("Generate a shard matrix for a flake")) +struct shard_matrix_args { - const params = parseEnv!Params; - auto matrix = generateShardMatrix(); - saveShardMatrix(matrix, params); - + @(NamedArgument(["github-output"]).Placeholder("output").Description("Output to GitHub Actions")) + string githubOutput; } -struct Params +export int shard_matrix(shard_matrix_args args) { - @optional() string githubOutput; + auto matrix = generateShardMatrix(); + saveShardMatrix(matrix, args); + return 0; - void setup() - { - } } struct Shard @@ -138,15 +138,15 @@ unittest } -void saveShardMatrix(ShardMatrix matrix, Params params) +void saveShardMatrix(ShardMatrix matrix, shard_matrix_args args) { const matrixJson = matrix.toJSON(); const matrixString = matrixJson.toString(); infof("Shard matrix: %s", matrixJson.toPrettyString); const envLine = "gen_matrix=" ~ matrixString; - if (params.githubOutput != "") + if (args.githubOutput != "") { - params.githubOutput.append(envLine); + args.githubOutput.append(envLine); } else {