Removed runner and made actual cli project.
This commit is contained in:
parent
ed6db6298d
commit
116bfa07bd
|
@ -43,6 +43,7 @@ public class SecurityConfig {
|
||||||
.authorizeHttpRequests()
|
.authorizeHttpRequests()
|
||||||
.requestMatchers(// Allow the following GET endpoints to be public.
|
.requestMatchers(// Allow the following GET endpoints to be public.
|
||||||
HttpMethod.GET,
|
HttpMethod.GET,
|
||||||
|
"/status",
|
||||||
"/exercises",
|
"/exercises",
|
||||||
"/leaderboards",
|
"/leaderboards",
|
||||||
"/gyms/**",
|
"/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