Removed runner and made actual cli project.
This commit is contained in:
parent
ed6db6298d
commit
116bfa07bd
|
@ -43,6 +43,7 @@ public class SecurityConfig {
|
|||
.authorizeHttpRequests()
|
||||
.requestMatchers(// Allow the following GET endpoints to be public.
|
||||
HttpMethod.GET,
|
||||
"/status",
|
||||
"/exercises",
|
||||
"/leaderboards",
|
||||
"/gyms/**",
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
.dub
|
||||
docs.json
|
||||
__dummy.html
|
||||
docs/
|
||||
/gymboard-cli
|
||||
gymboard-cli.so
|
||||
gymboard-cli.dylib
|
||||
gymboard-cli.dll
|
||||
gymboard-cli.a
|
||||
gymboard-cli.lib
|
||||
gymboard-cli-test-*
|
||||
*.exe
|
||||
*.pdb
|
||||
*.o
|
||||
*.obj
|
||||
*.lst
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"authors": [
|
||||
"Andrew Lalis"
|
||||
],
|
||||
"copyright": "Copyright © 2023, Andrew Lalis",
|
||||
"description": "CLI for developing Gymboard",
|
||||
"license": "proprietary",
|
||||
"name": "gymboard-cli"
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import std.stdio;
|
||||
|
||||
import cli;
|
||||
import services;
|
||||
|
||||
void main() {
|
||||
ServiceManager serviceManager = new ServiceManager();
|
||||
CliHandler cliHandler = new CliHandler();
|
||||
cliHandler.register("service", new ServiceCommand(serviceManager));
|
||||
writeln("Gymboard CLI: Type \"help\" for more information. Type \"exit\" to exit the CLI.");
|
||||
while (!cliHandler.shouldExit) {
|
||||
cliHandler.readAndHandleCommand();
|
||||
}
|
||||
serviceManager.stopAll();
|
||||
writeln("Goodbye!");
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
module cli;
|
||||
|
||||
import std.stdio;
|
||||
import std.string;
|
||||
import std.uni;
|
||||
|
||||
interface CliCommand {
|
||||
void handle(string[] args);
|
||||
}
|
||||
|
||||
class CliHandler {
|
||||
private CliCommand[string] commands;
|
||||
public bool shouldExit = false;
|
||||
|
||||
public void register(string name, CliCommand command) {
|
||||
this.commands[name] = command;
|
||||
}
|
||||
|
||||
public void readAndHandleCommand() {
|
||||
string[] commandAndArgs = readln().strip().split!isWhite();
|
||||
if (commandAndArgs.length == 0) return;
|
||||
string command = commandAndArgs[0].toLower();
|
||||
if (command == "help") {
|
||||
showHelp();
|
||||
} else if (command == "exit") {
|
||||
shouldExit = true;
|
||||
} else if (command in commands) {
|
||||
commands[command].handle(commandAndArgs.length > 1 ? commandAndArgs[1 .. $] : []);
|
||||
} else {
|
||||
writefln!"Unknown command \"%s\"."(command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void showHelp() {
|
||||
writeln(q"HELP
|
||||
Gymboard CLI: A tool for streamlining development.
|
||||
|
||||
Commands:
|
||||
|
||||
help Shows this message.
|
||||
exit Exits the CLI, stopping any running services.
|
||||
HELP");
|
||||
}
|
||||
|
||||
import services;
|
||||
|
||||
class ServiceCommand : CliCommand {
|
||||
private ServiceManager serviceManager;
|
||||
|
||||
public this(ServiceManager serviceManager) {
|
||||
this.serviceManager = serviceManager;
|
||||
}
|
||||
|
||||
void handle(string[] args) {
|
||||
if (args.length == 0) {
|
||||
writeln("Missing subcommand.");
|
||||
return;
|
||||
}
|
||||
string subcommand = args[0];
|
||||
if (subcommand == "status") {
|
||||
auto statuses = serviceManager.getStatus();
|
||||
if (statuses.length == 0) {
|
||||
writeln("No services running.");
|
||||
}
|
||||
foreach (status; statuses) {
|
||||
writefln!"%s: Running = %s, Exit code = %s"(status.name, status.running, status.exitCode);
|
||||
}
|
||||
} else if (subcommand == "start") {
|
||||
if (args.length < 2) {
|
||||
writeln("Missing service name.");
|
||||
return;
|
||||
}
|
||||
auto result = serviceManager.startService(args[1]);
|
||||
writeln(result.msg);
|
||||
} else if (subcommand == "stop") {
|
||||
if (args.length < 2) {
|
||||
writeln("Missing service name.");
|
||||
return;
|
||||
}
|
||||
auto result = serviceManager.stopService(args[1]);
|
||||
writeln(result.msg);
|
||||
} else {
|
||||
writeln("Unknown subcommand.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,159 @@
|
|||
module services;
|
||||
|
||||
import std.process;
|
||||
import std.stdio;
|
||||
import std.string;
|
||||
import std.typecons;
|
||||
import core.thread;
|
||||
|
||||
struct ServiceInfo {
|
||||
string name;
|
||||
string[] dependencies;
|
||||
string workingDir;
|
||||
string startupCommand;
|
||||
}
|
||||
|
||||
const SERVICES = [
|
||||
ServiceInfo(
|
||||
"api",
|
||||
["docker-deps"],
|
||||
"../gymboard-api",
|
||||
"./gen_keys.d && ./mvnw spring-boot:run -Dspring-boot.run.profiles=development"
|
||||
),
|
||||
ServiceInfo(
|
||||
"cdn",
|
||||
[],
|
||||
"../gymboard-cdn",
|
||||
"./mvnw spring-boot:run -Dspring-boot.run.profiles=development"
|
||||
),
|
||||
ServiceInfo(
|
||||
"search",
|
||||
["docker-deps"],
|
||||
"../gymboard-search",
|
||||
"./mvnw spring-boot:run -Dspring-boot.run.profiles=development"
|
||||
),
|
||||
ServiceInfo(
|
||||
"app",
|
||||
[],
|
||||
"../gymboard-app",
|
||||
"npm install && quasar dev"
|
||||
),
|
||||
ServiceInfo(
|
||||
"docker-deps",
|
||||
[],
|
||||
"../",
|
||||
"docker-compose up"
|
||||
)
|
||||
];
|
||||
|
||||
Nullable!(const(ServiceInfo)) getServiceByName(string name) {
|
||||
static foreach (service; SERVICES) {
|
||||
if (service.name == name) return nullable(service);
|
||||
}
|
||||
return Nullable!(const(ServiceInfo)).init;
|
||||
}
|
||||
|
||||
struct ServiceStatus {
|
||||
string name;
|
||||
bool running;
|
||||
Nullable!int exitCode;
|
||||
}
|
||||
|
||||
class ServiceManager {
|
||||
private ServiceRunner[string] serviceRunners;
|
||||
|
||||
public Tuple!(bool, "started", string, "msg") startService(string name) {
|
||||
auto info = getServiceByName(name);
|
||||
if (info.isNull) return tuple!("started", "msg")(false, "Invalid service name.");
|
||||
const ServiceInfo service = info.get();
|
||||
if (service.name !in serviceRunners || !serviceRunners[service.name].isRunning) {
|
||||
// Start all dependencies first.
|
||||
foreach (string depName; service.dependencies) {
|
||||
auto result = startService(depName);
|
||||
if (!result.started) {
|
||||
return tuple!("started", "msg")(
|
||||
false,
|
||||
format!"Couldn't start dependency \"%s\": %s"(depName, result.msg)
|
||||
);
|
||||
}
|
||||
}
|
||||
// Then start the process.
|
||||
writefln!"Starting service: %s"(service.name);
|
||||
ProcessPipes pipes = pipeShell(
|
||||
service.startupCommand,
|
||||
Redirect.all,
|
||||
null,
|
||||
Config.none,
|
||||
service.workingDir
|
||||
);
|
||||
ServiceRunner runner = new ServiceRunner(pipes);
|
||||
runner.start();
|
||||
serviceRunners[service.name] = runner;
|
||||
return tuple!("started", "msg")(true, "Service started.");
|
||||
}
|
||||
return tuple!("started", "msg")(true, "Service already running.");
|
||||
}
|
||||
|
||||
public Tuple!(bool, "stopped", string, "msg") stopService(string name) {
|
||||
auto info = getServiceByName(name);
|
||||
if (info.isNull) return tuple!("stopped", "msg")(false, "Invalid service name.");
|
||||
const ServiceInfo service = info.get();
|
||||
if (service.name in serviceRunners && serviceRunners[service.name].isRunning) {
|
||||
int exitStatus = serviceRunners[service.name].stopService();
|
||||
return tuple!("stopped", "msg")(
|
||||
true,
|
||||
format!"Service exited with status %d."(exitStatus)
|
||||
);
|
||||
}
|
||||
return tuple!("stopped", "msg")(true, "Service already stopped.");
|
||||
}
|
||||
|
||||
public void stopAll() {
|
||||
foreach (name, runner; serviceRunners) {
|
||||
runner.stopService();
|
||||
}
|
||||
}
|
||||
|
||||
public ServiceStatus[] getStatus() {
|
||||
ServiceStatus[] statuses;
|
||||
foreach (name, runner; serviceRunners) {
|
||||
statuses ~= ServiceStatus(name, runner.isRunning, runner.exitStatus);
|
||||
}
|
||||
return statuses;
|
||||
}
|
||||
}
|
||||
|
||||
class ServiceRunner : Thread {
|
||||
private Pid processId;
|
||||
private File processStdin;
|
||||
private File processStdout;
|
||||
private File processStderr;
|
||||
public Nullable!int exitStatus;
|
||||
|
||||
public this(ProcessPipes pipes) {
|
||||
super(&this.run);
|
||||
this.processId = pipes.pid();
|
||||
this.processStdin = pipes.stdin();
|
||||
this.processStdout = pipes.stdout();
|
||||
this.processStderr = pipes.stderr();
|
||||
}
|
||||
|
||||
private void run() {
|
||||
Tuple!(bool, "terminated", int, "status") result = tryWait(this.processId);
|
||||
while (!result.terminated) {
|
||||
Thread.sleep(msecs(1000));
|
||||
result = tryWait(this.processId);
|
||||
}
|
||||
this.exitStatus = result.status;
|
||||
}
|
||||
|
||||
public int stopService() {
|
||||
version(Posix) {
|
||||
import core.sys.posix.signal : SIGTERM;
|
||||
kill(this.processId, SIGTERM);
|
||||
} else version(Windows) {
|
||||
kill(this.processId);
|
||||
}
|
||||
return wait(this.processId);
|
||||
}
|
||||
}
|
67
runner.d
67
runner.d
|
@ -1,67 +0,0 @@
|
|||
#!/usr/bin/env rdmd
|
||||
|
||||
/**
|
||||
* TODO: This module will eventually serve as some sort of setup script for
|
||||
* if/when it becomes too complicated to just start the services. It should
|
||||
* run as a CLI thing for entering commands to start/stop things.
|
||||
*/
|
||||
module runner;
|
||||
|
||||
import std.process;
|
||||
import std.stdio;
|
||||
import std.string;
|
||||
import std.uni;
|
||||
import core.thread;
|
||||
|
||||
int main() {
|
||||
bool running = true;
|
||||
writeln("Gymboard CLI: Type \"help\" for more information. Type \"exit\" to exit the CLI.");
|
||||
while (running) {
|
||||
string[] commandAndArgs = readln().strip.split!isWhite;
|
||||
if (commandAndArgs.length == 0) continue;
|
||||
string command = commandAndArgs[0].toLower();
|
||||
if (command == "help") {
|
||||
showHelp();
|
||||
} else if (command == "exit") {
|
||||
running = false;
|
||||
} else if (command in commands) {
|
||||
commands[command](commandAndArgs.length > 1 ? commandAndArgs[1 .. $] : []);
|
||||
} else {
|
||||
writefln!"Unknown command \"%s\"."(command);
|
||||
}
|
||||
}
|
||||
writeln("Goodbye!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
alias CommandFunction = void function(string[] args);
|
||||
|
||||
CommandFunction[string] commands;
|
||||
|
||||
void registerCommand(string name, CommandFunction func) {
|
||||
commands[name] = func;
|
||||
}
|
||||
|
||||
void showHelp() {
|
||||
writeln(q"HELP
|
||||
Gymboard CLI: A tool for streamlining development.
|
||||
|
||||
Commands:
|
||||
|
||||
help Shows this message.
|
||||
exit Exits the CLI, stopping any running services.
|
||||
HELP");
|
||||
}
|
||||
|
||||
class ProcessRunner : Thread {
|
||||
private Pid processId;
|
||||
|
||||
public this(ProcessPipes pipes) {
|
||||
super(&this.run);
|
||||
this.processId = pipes.pid();
|
||||
}
|
||||
|
||||
private void run() {
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue