From 1d3bb634f0f0c2b72ce49247abe63fef130cecd9 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 17 May 2025 19:40:51 +1200 Subject: [PATCH] Refact0r --- source/src/commands/create-service.cpp | 133 ++++++++++ source/src/commands/edit.cpp | 2 +- source/src/commands/health.cpp | 71 +---- source/src/commands/install.cpp | 45 +--- source/src/commands/list.cpp | 11 +- source/src/commands/nuke.cpp | 3 +- source/src/commands/shared_commands.cpp | 247 ++++++++++++++++++ source/src/commands/shared_commands.hpp | 54 +++- source/src/commands/standard_autocomplete.cpp | 36 --- source/src/commands/uninstall.cpp | 10 +- source/src/main.cpp | 173 ++++++------ source/src/servers.cpp | 1 - source/src/servers.hpp | 3 +- source/src/service_runner.cpp | 147 +++++------ source/src/service_runner.hpp | 102 -------- source/src/services.cpp | 67 ----- source/src/services.hpp | 2 - 17 files changed, 608 insertions(+), 499 deletions(-) create mode 100644 source/src/commands/create-service.cpp create mode 100644 source/src/commands/shared_commands.cpp delete mode 100644 source/src/commands/standard_autocomplete.cpp delete mode 100644 source/src/service_runner.hpp diff --git a/source/src/commands/create-service.cpp b/source/src/commands/create-service.cpp new file mode 100644 index 0000000..578f55b --- /dev/null +++ b/source/src/commands/create-service.cpp @@ -0,0 +1,133 @@ +#include "command_registry.hpp" +#include "directories.hpp" +#include "shared_commands.hpp" +#include "templates.hpp" + +#include "utils/assert.hpp" +#include "utils/utils.hpp" +#include "services.hpp" + +namespace dropshell +{ + + int create_service_handler(const CommandContext &ctx); + bool create_service(const std::string &server_name, const std::string &template_name, const std::string &service_name, bool silent); + void create_service_autocomplete(const CommandContext &ctx); + + static std::vector create_service_name_list = {"create-service"}; + + // Static registration + struct UninstallCommandRegister + { + UninstallCommandRegister() + { + CommandRegistry::instance().register_command({create_service_name_list, + create_service_handler, + create_service_autocomplete, + false, // hidden + true, // requires_config + true, // requires_install + 2, // min_args (after command) + 2, // max_args (after command) + "create-service SERVER SERVICE TEMPLATE", + "Create a service on a server.", + // heredoc + R"( + Create a service on a server. + create-service SERVER SERVICE TEMPLATE create the given service on the given server. + )"}); + } + } create_service_command_register; + + int create_service_handler(const CommandContext &ctx) + { + std::string server = safearg(ctx.args, 0); + std::string service = safearg(ctx.args, 1); + std::string template_name = safearg(ctx.args, 2); + + return create_service(server, template_name, service, false) ? 0 : 1; + } + + void create_service_autocomplete(const CommandContext &ctx) + { + if (ctx.args.size() < 2) + shared_commands::std_autocomplete(ctx); + else + { + if (ctx.args.size() == 2) + { + std::set templates = gTemplateManager().get_template_list(); + for (const auto &template_name : templates) + std::cout << template_name << std::endl; + } + } + } + + bool create_service(const std::string &server_name, const std::string &template_name, const std::string &service_name, bool silent) + { + if (server_name.empty() || template_name.empty() || service_name.empty()) + return false; + + std::string service_dir = localpath::service(server_name, service_name); + + if (service_dir.empty()) + { + if (!silent) + { + std::cerr << "Error: Couldn't locate server " << server_name << " in any config directory" << std::endl; + std::cerr << "Please check the server name is correct and try again" << std::endl; + std::cerr << "You can list all servers with 'dropshell servers'" << std::endl; + std::cerr << "You can create a new server with 'dropshell create-server " << server_name << "'" << std::endl; + } + return false; + } + + if (std::filesystem::exists(service_dir)) + { + if (!silent) + { + std::cerr << "Error: Service already exists: " << service_name << std::endl; + std::cerr << "Current service path: " << service_dir << std::endl; + } + return false; + } + + template_info tinfo = gTemplateManager().get_template_info(template_name); + if (!tinfo.is_set()) + { + if (!silent) + { + std::cerr << "Error: Template '" << template_name << "' not found" << std::endl; + std::cerr << "Please check the template name is correct and try again" << std::endl; + std::cerr << "You can list all templates with 'dropshell templates'" << std::endl; + std::cerr << "You can create a new template with 'dropshell create-template " << template_name << "'" << std::endl; + } + return false; + } + + // check template is all good. + if (!gTemplateManager().test_template(tinfo.local_template_path())) + { + if (!silent) + std::cerr << "Error: Template '" << template_name << "' is not valid" << std::endl; + return false; + } + + // create the service directory + std::filesystem::create_directory(service_dir); + + // copy the template config files to the service directory + recursive_copy(tinfo.local_template_path() / "config", service_dir); + + if (!silent) + { + std::cout << "Service " << service_name << " created successfully" << std::endl; + std::cout << std::endl; + std::cout << "To complete the installation, please:" << std::endl; + std::cout << "1. edit the service config file: dropshell edit " << server_name << " " << service_name << std::endl; + std::cout << "2. install the remote service: dropshell install " << server_name << " " << service_name << std::endl; + } + return true; + } + +} // namespace dropshell \ No newline at end of file diff --git a/source/src/commands/edit.cpp b/source/src/commands/edit.cpp index 0c57be8..9554068 100644 --- a/source/src/commands/edit.cpp +++ b/source/src/commands/edit.cpp @@ -23,7 +23,7 @@ struct EditCommandRegister { CommandRegistry::instance().register_command({ edit_name_list, edit_handler, - std_autocomplete, + shared_commands::std_autocomplete, false, // hidden false, // requires_config false, // requires_install diff --git a/source/src/commands/health.cpp b/source/src/commands/health.cpp index 03f5157..2a9bdb6 100644 --- a/source/src/commands/health.cpp +++ b/source/src/commands/health.cpp @@ -22,7 +22,7 @@ namespace dropshell { CommandRegistry::instance().register_command({health_name_list, health_handler, - std_autocomplete_allowall, + shared_commands::std_autocomplete_allowall, false, // hidden true, // requires_config true, // requires_install @@ -36,72 +36,7 @@ namespace dropshell } } health_command_register; - // ------------------------------------------------------------------------------------------------ - // health command implementation - HealthStatus is_healthy(const std::string &server, const std::string &service) - { - server_env_manager env(server); - if (!env.is_valid()) - { - std::cerr << "Error: Server service not initialized" << std::endl; - return HealthStatus::ERROR; - } - - if (!env.check_remote_dir_exists(remotepath::service(server, service))) - { - return HealthStatus::NOTINSTALLED; - } - - std::string script_path = remotepath::service_template(server, service) + "/status.sh"; - if (!env.check_remote_file_exists(script_path)) - { - return HealthStatus::UNKNOWN; - } - - // Run status script, does not display output. - if (!env.run_remote_template_command(service, "status", {}, true, {})) - return HealthStatus::UNHEALTHY; - return HealthStatus::HEALTHY; - } - - std::string healthtick(const std::string &server, const std::string &service) - { - std::string green_tick = "\033[32m✓\033[0m"; - std::string red_cross = "\033[31m✗\033[0m"; - std::string yellow_exclamation = "\033[33m!\033[0m"; - std::string unknown = "\033[37m✓\033[0m"; - - HealthStatus status = is_healthy(server, service); - if (status == HealthStatus::HEALTHY) - return green_tick; - else if (status == HealthStatus::UNHEALTHY) - return red_cross; - else if (status == HealthStatus::UNKNOWN) - return unknown; - else - return yellow_exclamation; - } - - std::string HealthStatus2String(HealthStatus status) - { - if (status == HealthStatus::HEALTHY) - return ":tick:"; - else if (status == HealthStatus::UNHEALTHY) - return ":cross:"; - else if (status == HealthStatus::UNKNOWN) - return ":greytick:"; - else if (status == HealthStatus::NOTINSTALLED) - return ":warning:"; - else - return ":error:"; - } - - std::string healthmark(const std::string &server, const std::string &service) - { - HealthStatus status = is_healthy(server, service); - return HealthStatus2String(status); - } // ------------------------------------------------------------------------------------------------ // health command implementation @@ -121,7 +56,7 @@ namespace dropshell std::vector services = get_server_services_info(server); transwarp::parallel exec{services.size()}; auto task = transwarp::for_each(exec, services.begin(), services.end(), [&](const LocalServiceInfo& service) { - std::string status = healthtick(server, service.service_name); + std::string status = shared_commands::healthtick(server, service.service_name); std::cout << status << " " << service.service_name << " (" << service.template_name << ")" << std::endl << std::flush; }); task->wait(); @@ -130,7 +65,7 @@ namespace dropshell // get service status std::string service = safearg(ctx.args, 1); LocalServiceInfo service_info = get_service_info(server, service); - std::cout << healthtick(server, service) << " " << service << " (" << service_info.template_name << ")" << std::endl << std::flush; + std::cout << shared_commands::healthtick(server, service) << " " << service << " (" << service_info.template_name << ")" << std::endl << std::flush; } return 0; } diff --git a/source/src/commands/install.cpp b/source/src/commands/install.cpp index 9762b82..71f2339 100644 --- a/source/src/commands/install.cpp +++ b/source/src/commands/install.cpp @@ -6,6 +6,8 @@ #include "shared_commands.hpp" #include "utils/hash.hpp" #include "autogen/_agent.hpp" +#include "services.hpp" + #include #include #include @@ -27,7 +29,7 @@ namespace dropshell { CommandRegistry::instance().register_command({install_name_list, install_handler, - std_autocomplete_allowall, + shared_commands::std_autocomplete_allowall, false, // hidden false, // requires_config false, // requires_install @@ -50,23 +52,6 @@ namespace dropshell } } install_command_register; - // ------------------------------------------------------------------------------------------------ - // rsync_tree_to_remote : SHARED COMMAND - // ------------------------------------------------------------------------------------------------ - bool rsync_tree_to_remote( - const std::string &local_path, - const std::string &remote_path, - server_env_manager &server_env, - bool silent) - { - ASSERT(!local_path.empty() && !remote_path.empty(), "Local or remote path not specified. Can't rsync."); - - std::string rsync_cmd = "rsync --delete --mkpath -zrpc -e 'ssh -p " + server_env.get_SSH_PORT() + "' " + - quote(local_path + "/") + " " + - quote(server_env.get_SSH_USER() + "@" + server_env.get_SSH_HOST() + ":" + - remote_path + "/"); - return execute_local_command(rsync_cmd, nullptr, (silent ? cMode::Silent : cMode::Defaults)); - } // ------------------------------------------------------------------------------------------------ // install service over ssh : SHARED COMMAND @@ -117,7 +102,7 @@ namespace dropshell // Copy template files std::cout << "Copying: [LOCAL] " << tinfo.local_template_path() << std::endl << std::string(8, ' ') << "[REMOTE] " << remotepath::service_template(server, service) << "/" << std::endl; - if (!rsync_tree_to_remote(tinfo.local_template_path().string(), remotepath::service_template(server, service), + if (!shared_commands::rsync_tree_to_remote(tinfo.local_template_path().string(), remotepath::service_template(server, service), server_env, silent)) { std::cerr << "Failed to copy template files using rsync" << std::endl; @@ -127,7 +112,7 @@ namespace dropshell // Copy service files std::cout << "Copying: [LOCAL] " << localpath::service(server, service) << std::endl << std::string(8, ' ') << "[REMOTE] " << remotepath::service_config(server, service) << std::endl; - if (!rsync_tree_to_remote(localpath::service(server, service), remotepath::service_config(server, service), + if (!shared_commands::rsync_tree_to_remote(localpath::service(server, service), remotepath::service_config(server, service), server_env, silent)) { std::cerr << "Failed to copy service files using rsync" << std::endl; @@ -140,24 +125,10 @@ namespace dropshell } // print health tick - std::cout << "Health: " << healthtick(server, service) << std::endl; + std::cout << "Health: " << shared_commands::healthtick(server, service) << std::endl; return true; } - // ------------------------------------------------------------------------------------------------ - // get_arch : SHARED COMMAND - // ------------------------------------------------------------------------------------------------ - std::string get_arch() - { - // determine the architecture of the system - std::string arch; -#ifdef __aarch64__ - arch = "arm64"; -#elif __x86_64__ - arch = "amd64"; -#endif - return arch; - } // ------------------------------------------------------------------------------------------------ // update_dropshell @@ -188,7 +159,7 @@ namespace dropshell std::filesystem::path parent_path = dropshell_path.parent_path(); // determine the architecture of the system - std::string arch = get_arch(); + std::string arch = shared_commands::get_arch(); std::string url = "https://gitea.jde.nz/public/dropshell/releases/download/latest/dropshell." + arch; @@ -327,7 +298,7 @@ namespace dropshell // now create the agent. // copy across from the local agent files. - rsync_tree_to_remote(localpath::files_for_remote_agent(), agent_path, server_env, false); + shared_commands::rsync_tree_to_remote(localpath::files_for_remote_agent(), agent_path, server_env, false); // add in bb64. We can't use execute_remote_command() here, as that relies on bb64 which we're installing! std::cout << "Installing bb64 on " << server << std::endl << std::flush; diff --git a/source/src/commands/list.cpp b/source/src/commands/list.cpp index 2160187..48c5d5b 100644 --- a/source/src/commands/list.cpp +++ b/source/src/commands/list.cpp @@ -6,6 +6,7 @@ #include "servers.hpp" #include "tableprint.hpp" #include "transwarp.hpp" +#include "server_env_manager.hpp" #include #include @@ -28,7 +29,7 @@ struct ListCommandRegister { CommandRegistry::instance().register_command({ list_name_list, list_handler, - std_autocomplete, + shared_commands::std_autocomplete, false, // hidden true, // requires_config true, // requires_install @@ -86,13 +87,13 @@ void list_servers() { transwarp::parallel exec{servers.size()}; auto task = transwarp::for_each(exec, servers.begin(), servers.end(), [&](const ServerInfo& server) { - std::map status = service_runner::get_all_services_status(server.name); + std::map status = shared_commands::get_all_services_status(server.name); std::set ports_used; std::string serviceticks = ""; for (const auto& [service_name, service_status] : status) { ports_used.insert(service_status.ports.begin(), service_status.ports.end()); - serviceticks += HealthStatus2String(service_status.health) + " "; + serviceticks += shared_commands::HealthStatus2String(service_status.health) + " "; } std::string ports_used_str = ""; for (const auto& port : ports_used) @@ -163,12 +164,12 @@ void show_server_details(const std::string& server_name) { tp.add_row({"Status", "Service", "Ports"}); - std::map status = service_runner::get_all_services_status(server_name); + std::map status = shared_commands::get_all_services_status(server_name); std::set ports_used; std::string serviceticks = ""; for (const auto& [service_name, service_status] : status) { - std::string healthy = HealthStatus2String(service_status.health); + std::string healthy = shared_commands::HealthStatus2String(service_status.health); std::string ports_str = ""; for (const auto& port : service_status.ports) diff --git a/source/src/commands/nuke.cpp b/source/src/commands/nuke.cpp index fa40f7b..2f6bfc0 100644 --- a/source/src/commands/nuke.cpp +++ b/source/src/commands/nuke.cpp @@ -6,6 +6,7 @@ #include "utils/directories.hpp" #include "servers.hpp" #include "templates.hpp" +#include "utils/utils.hpp" #include "utils/assert.hpp" @@ -22,7 +23,7 @@ struct NukeCommandRegister { CommandRegistry::instance().register_command({ nuke_name_list, nuke_handler, - std_autocomplete, + shared_commands::std_autocomplete, false, // hidden true, // requires_config true, // requires_install diff --git a/source/src/commands/shared_commands.cpp b/source/src/commands/shared_commands.cpp new file mode 100644 index 0000000..bfe478f --- /dev/null +++ b/source/src/commands/shared_commands.cpp @@ -0,0 +1,247 @@ +#include "shared_commands.hpp" +#include "utils/assert.hpp" +#include "utils/utils.hpp" +#include "server_env_manager.hpp" +#include "directories.hpp" +#include "services.hpp" +#include "servers.hpp" + +namespace dropshell +{ + + namespace shared_commands + { + + // ------------------------------------------------------------------------------------------------ + // std_autocomplete : SHARED COMMAND + // ------------------------------------------------------------------------------------------------ + void std_autocomplete(const CommandContext &ctx) + { + if (ctx.args.size() == 0) + { // just the command, no args yet. + // list servers + std::vector servers = get_configured_servers(); + for (const auto &server : servers) + { + std::cout << server.name << std::endl; + } + } + else if (ctx.args.size() == 1) + { + // list services + std::vector services = get_server_services_info(ctx.args[0]); + for (const auto &service : services) + { + std::cout << service.service_name << std::endl; + } + } + } + + // ------------------------------------------------------------------------------------------------ + // std_autocomplete_allowall : SHARED COMMAND + // ------------------------------------------------------------------------------------------------ + void std_autocomplete_allowall(const CommandContext &ctx) + { + std_autocomplete(ctx); + if (ctx.args.size() == 1) + std::cout << "all" << std::endl; + } + + // ------------------------------------------------------------------------------------------------ + // rsync_tree_to_remote : SHARED COMMAND + // ------------------------------------------------------------------------------------------------ + bool rsync_tree_to_remote( + const std::string &local_path, + const std::string &remote_path, + server_env_manager &server_env, + bool silent) + { + ASSERT(!local_path.empty() && !remote_path.empty(), "Local or remote path not specified. Can't rsync."); + + std::string rsync_cmd = "rsync --delete --mkpath -zrpc -e 'ssh -p " + server_env.get_SSH_PORT() + "' " + + quote(local_path + "/") + " " + + quote(server_env.get_SSH_USER() + "@" + server_env.get_SSH_HOST() + ":" + + remote_path + "/"); + return execute_local_command(rsync_cmd, nullptr, (silent ? cMode::Silent : cMode::Defaults)); + } + + // ------------------------------------------------------------------------------------------------ + // get_arch : SHARED COMMAND + // ------------------------------------------------------------------------------------------------ + std::string get_arch() + { + // determine the architecture of the system + std::string arch; +#ifdef __aarch64__ + arch = "arm64"; +#elif __x86_64__ + arch = "amd64"; +#endif + return arch; + } + + // ------------------------------------------------------------------------------------------------ + // cRemoteTempFolder : SHARED CLASS + // ------------------------------------------------------------------------------------------------ + cRemoteTempFolder::cRemoteTempFolder(const server_env_manager &server_env) : mServerEnv(server_env) + { + std::string p = remotepath::temp_files(server_env.get_server_name()) + "/" + random_alphanumeric_string(10); + std::string mkdir_cmd = "mkdir -p " + quote(p); + if (!execute_ssh_command(server_env.get_SSH_INFO(), sCommand("", mkdir_cmd, {}), cMode::Silent)) + std::cerr << "Failed to create temp directory on server" << std::endl; + else + mPath = p; + } + + cRemoteTempFolder::~cRemoteTempFolder() + { + std::string rm_cmd = "rm -rf " + quote(mPath); + execute_ssh_command(mServerEnv.get_SSH_INFO(), sCommand("", rm_cmd, {}), cMode::Silent); + } + + std::string cRemoteTempFolder::path() const + { + return mPath; + } + + // ------------------------------------------------------------------------------------------------ + // get_all_services_status : SHARED COMMAND + // ------------------------------------------------------------------------------------------------ + std::map get_all_services_status(std::string server_name) + { + std::map status; + + server_env_manager env(server_name); + if (!env.is_valid()) + { + std::cerr << "Error: Invalid server environment" << std::endl; + return status; + } + + std::string output; + if (!execute_ssh_command(env.get_SSH_INFO(), sCommand(remotepath::agent(server_name), "./_allservicesstatus.sh", {{"HOST_NAME", server_name}, {"SERVER", server_name}, {"AGENT_PATH", remotepath::agent(server_name)}}), cMode::CaptureOutput, &output)) + return status; + + std::stringstream ss(output); + std::string line; + while (std::getline(ss, line)) + { + std::string key, value; + std::size_t pos = line.find("="); + if (pos != std::string::npos) + { + key = dequote(trim(line.substr(0, pos))); + value = dequote(trim(line.substr(pos + 1))); + + // decode key, it's of format SERVICENAME_[HEALTH|PORTS] + std::string service_name = key.substr(0, key.find_last_of("_")); + std::string status_type = key.substr(key.find_last_of("_") + 1); + + if (status_type == "HEALTH") + { // healthy|unhealthy|unknown + if (value == "healthy") + status[service_name].health = HealthStatus::HEALTHY; + else if (value == "unhealthy") + status[service_name].health = HealthStatus::UNHEALTHY; + else if (value == "unknown") + status[service_name].health = HealthStatus::UNKNOWN; + else + status[service_name].health = HealthStatus::ERROR; + } + else if (status_type == "PORTS") + { // port1,port2,port3 + std::vector ports = string2multi(value); + for (const auto &port : ports) + { + if (port != "unknown") + status[service_name].ports.push_back(str2int(port)); + } + } + } + } + return status; + } + + + + // ------------------------------------------------------------------------------------------------ + // healthtick : SHARED COMMAND + // ------------------------------------------------------------------------------------------------ + std::string healthtick(const std::string &server, const std::string &service) + { + std::string green_tick = "\033[32m✓\033[0m"; + std::string red_cross = "\033[31m✗\033[0m"; + std::string yellow_exclamation = "\033[33m!\033[0m"; + std::string unknown = "\033[37m✓\033[0m"; + + HealthStatus status = is_healthy(server, service); + if (status == HealthStatus::HEALTHY) + return green_tick; + else if (status == HealthStatus::UNHEALTHY) + return red_cross; + else if (status == HealthStatus::UNKNOWN) + return unknown; + else + return yellow_exclamation; + } + + // ------------------------------------------------------------------------------------------------ + // HealthStatus2String : SHARED COMMAND + // ------------------------------------------------------------------------------------------------ + std::string HealthStatus2String(HealthStatus status) + { + if (status == HealthStatus::HEALTHY) + return ":tick:"; + else if (status == HealthStatus::UNHEALTHY) + return ":cross:"; + else if (status == HealthStatus::UNKNOWN) + return ":greytick:"; + else if (status == HealthStatus::NOTINSTALLED) + return ":warning:"; + else + return ":error:"; + } + + + // ------------------------------------------------------------------------------------------------ + // is_healthy : SHARED COMMAND + // ------------------------------------------------------------------------------------------------ + HealthStatus is_healthy(const std::string &server, const std::string &service) + { + server_env_manager env(server); + if (!env.is_valid()) + { + std::cerr << "Error: Server service not initialized" << std::endl; + return HealthStatus::ERROR; + } + + if (!env.check_remote_dir_exists(remotepath::service(server, service))) + { + return HealthStatus::NOTINSTALLED; + } + + std::string script_path = remotepath::service_template(server, service) + "/status.sh"; + if (!env.check_remote_file_exists(script_path)) + { + return HealthStatus::UNKNOWN; + } + + // Run status script, does not display output. + if (!env.run_remote_template_command(service, "status", {}, true, {})) + return HealthStatus::UNHEALTHY; + return HealthStatus::HEALTHY; + } + + + // ------------------------------------------------------------------------------------------------ + // healthmark : SHARED COMMAND + // ------------------------------------------------------------------------------------------------ + std::string healthmark(const std::string &server, const std::string &service) + { + HealthStatus status = is_healthy(server, service); + return HealthStatus2String(status); + } + + } // namespace shared_commands + +} // namespace dropshell \ No newline at end of file diff --git a/source/src/commands/shared_commands.hpp b/source/src/commands/shared_commands.hpp index 7bfccd0..ad3ebe3 100644 --- a/source/src/commands/shared_commands.hpp +++ b/source/src/commands/shared_commands.hpp @@ -3,27 +3,61 @@ #include "servers.hpp" #include "command_registry.hpp" +#include "server_env_manager.hpp" -namespace dropshell { +namespace dropshell +{ + + namespace shared_commands + { + + typedef enum HealthStatus + { + HEALTHY, + UNHEALTHY, + NOTINSTALLED, + ERROR, + UNKNOWN + } HealthStatus; + + typedef struct ServiceStatus + { + HealthStatus health; + std::vector ports; + } ServiceStatus; + + // expose routines used by multiple commands. + + class cRemoteTempFolder + { + public: + cRemoteTempFolder(const server_env_manager &server_env); // create a temp folder on the remote server + ~cRemoteTempFolder(); // delete the temp folder on the remote server + std::string path() const; // get the path to the temp folder on the remote server + private: + std::string mPath; + const server_env_manager &mServerEnv; + }; - // defined in install.cpp bool rsync_tree_to_remote( const std::string &local_path, - const std::string &remote_path, - server_env_manager &server_env, + const std::string &remote_path, + server_env_manager &server_env, bool silent); - // defined in install.cpp std::string get_arch(); - // defined in health.cpp - std::string healthtick(const std::string& server, const std::string& service); + std::map get_all_services_status(std::string server_name); + + std::string healthtick(const std::string &server, const std::string &service); std::string HealthStatus2String(HealthStatus status); + HealthStatus is_healthy(const std::string &server, const std::string &service); + std::string healthmark(const std::string &server, const std::string &service); - // defined in standard_autocomplete.cpp - void std_autocomplete(const CommandContext& ctx); - void std_autocomplete_allowall(const CommandContext& ctx); + void std_autocomplete(const CommandContext &ctx); + void std_autocomplete_allowall(const CommandContext &ctx); + } // namespace shared_commands } // namespace dropshell #endif diff --git a/source/src/commands/standard_autocomplete.cpp b/source/src/commands/standard_autocomplete.cpp deleted file mode 100644 index 666b1e8..0000000 --- a/source/src/commands/standard_autocomplete.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include "shared_commands.hpp" -#include "command_registry.hpp" - -#include "servers.hpp" -#include "services.hpp" - -#include "utils/assert.hpp" - -namespace dropshell { - -void std_autocomplete(const CommandContext &ctx) -{ - if (ctx.args.size() == 0) { // just the command, no args yet. - // list servers - std::vector servers = get_configured_servers(); - for (const auto& server : servers) { - std::cout << server.name << std::endl; - } - } - else if (ctx.args.size() == 1) { - // list services - std::vector services = get_server_services_info(ctx.args[0]); - for (const auto& service : services) { - std::cout << service.service_name << std::endl; - } - } -} - -void std_autocomplete_allowall(const CommandContext &ctx) -{ - std_autocomplete(ctx); - if (ctx.args.size() == 1) - std::cout << "all" << std::endl; -} - -} // namespace dropshell \ No newline at end of file diff --git a/source/src/commands/uninstall.cpp b/source/src/commands/uninstall.cpp index 048658e..b0ca97e 100644 --- a/source/src/commands/uninstall.cpp +++ b/source/src/commands/uninstall.cpp @@ -4,6 +4,8 @@ #include "templates.hpp" #include "utils/assert.hpp" +#include "utils/utils.hpp" +#include "services.hpp" namespace dropshell { @@ -19,7 +21,7 @@ namespace dropshell { CommandRegistry::instance().register_command({uninstall_name_list, uninstall_handler, - std_autocomplete_allowall, + shared_commands::std_autocomplete_allowall, false, // hidden true, // requires_config true, // requires_install @@ -39,7 +41,7 @@ namespace dropshell - bool uninstall_service(const std::string &server, const std::string &service, bool silent) + bool uninstall_service(const std::string &server, const std::string &service, bool silent=false) { if (!silent) maketitle("Uninstalling " + service + " on " + server); @@ -97,14 +99,14 @@ namespace dropshell std::vector services = get_server_services_info(server); for (const auto &service : services) { - if (!uninstall_service(server, service.service_name, false)) + if (!uninstall_service(server, service.service_name)) okay = false; } return okay ? 0 : 1; } std::string service = safearg(ctx.args, 1); - return uninstall_service(server, service, false) ? 0 : 1; + return uninstall_service(server, service) ? 0 : 1; } diff --git a/source/src/main.cpp b/source/src/main.cpp index 470a029..139dc85 100644 --- a/source/src/main.cpp +++ b/source/src/main.cpp @@ -1,6 +1,5 @@ #include "version.hpp" #include "config.hpp" -#include "service_runner.hpp" #include "services.hpp" #include "servers.hpp" #include "utils/directories.hpp" @@ -148,113 +147,113 @@ auto command_match = [](const std::string& cmd_list, int argc, char* argv[]) -> } -int old_main(int argc, char* argv[]) { - HAPPYEXIT("hash", hash_demo_raw(safearg(argc,argv,2))) - HAPPYEXIT("version", printversion()) - BOOLEXIT("test-template", gTemplateManager().test_template(safearg(argc,argv,2))) - ASSERT(safearg(argc,argv,1) != "assert", "Hello! Here is an assert."); +// int old_main(int argc, char* argv[]) { +// HAPPYEXIT("hash", hash_demo_raw(safearg(argc,argv,2))) +// HAPPYEXIT("version", printversion()) +// BOOLEXIT("test-template", gTemplateManager().test_template(safearg(argc,argv,2))) +// ASSERT(safearg(argc,argv,1) != "assert", "Hello! Here is an assert."); - try { - // silently attempt to load the config file and templates. - gConfig().load_config(); - if (gConfig().is_config_set()) - gTemplateManager().load_sources(); +// try { +// // silently attempt to load the config file and templates. +// gConfig().load_config(); +// if (gConfig().is_config_set()) +// gTemplateManager().load_sources(); - std::string cmd = argv[1]; +// std::string cmd = argv[1]; - // ------------------------------------------------------------ - // from here we require the config file to be loaded. - if (!gConfig().is_config_set()) - return die("Please run 'dropshell edit' to set up the dropshell configuration."); +// // ------------------------------------------------------------ +// // from here we require the config file to be loaded. +// if (!gConfig().is_config_set()) +// return die("Please run 'dropshell edit' to set up the dropshell configuration."); - const std::vector & server_definition_paths = gConfig().get_local_server_definition_paths(); - if (server_definition_paths.size()>1) { // only show if there are multiple. - std::cout << "Server definition paths: "; - for (auto & dir : server_definition_paths) - std::cout << "["<< dir << "] "; - std::cout << std::endl; - } - if (gTemplateManager().is_loaded() && gTemplateManager().get_source_count() > 0) - gTemplateManager().print_sources(); +// const std::vector & server_definition_paths = gConfig().get_local_server_definition_paths(); +// if (server_definition_paths.size()>1) { // only show if there are multiple. +// std::cout << "Server definition paths: "; +// for (auto & dir : server_definition_paths) +// std::cout << "["<< dir << "] "; +// std::cout << std::endl; +// } +// if (gTemplateManager().is_loaded() && gTemplateManager().get_source_count() > 0) +// gTemplateManager().print_sources(); - HAPPYEXIT("templates", gTemplateManager().list_templates()); +// HAPPYEXIT("templates", gTemplateManager().list_templates()); - if (cmd == "create-template") { - if (argc < 3) return die("Error: create-template requires a template name"); - return (gTemplateManager().create_template(argv[2])) ? 0 : 1; - } +// if (cmd == "create-template") { +// if (argc < 3) return die("Error: create-template requires a template name"); +// return (gTemplateManager().create_template(argv[2])) ? 0 : 1; +// } - if (cmd == "create-server") { - if (argc < 3) return die("Error: create-server requires a server name"); - return (create_server(argv[2])) ? 0 : 1; - } +// if (cmd == "create-server") { +// if (argc < 3) return die("Error: create-server requires a server name"); +// return (create_server(argv[2])) ? 0 : 1; +// } - if (cmd == "create-service") { - if (argc < 5) return die("Error: not enough arguments.\ndropshell create-service server template service"); - return (create_service(argv[2], argv[3], argv[4])) ? 0 : 1; - } +// if (cmd == "create-service") { +// if (argc < 5) return die("Error: not enough arguments.\ndropshell create-service server template service"); +// return (create_service(argv[2], argv[3], argv[4])) ? 0 : 1; +// } - if (cmd == "ssh" && argc < 4) { - if (argc < 3) return die("Error: ssh requires a server name and optionally service name"); - service_runner::interactive_ssh(argv[2], "bash"); - return 0; - } +// if (cmd == "ssh" && argc < 4) { +// if (argc < 3) return die("Error: ssh requires a server name and optionally service name"); +// service_runner::interactive_ssh(argv[2], "bash"); +// return 0; +// } - // handle running a command. - std::set commands; - get_all_used_commands(commands); +// // handle running a command. +// std::set commands; +// get_all_used_commands(commands); - autocomplete::merge_commands(commands, autocomplete::service_commands_require_config); // handled by service_runner, but not in template_shell_commands. +// autocomplete::merge_commands(commands, autocomplete::service_commands_require_config); // handled by service_runner, but not in template_shell_commands. - if (commands.count(cmd)) { - std::set safe_commands = {"nuke", "fullnuke"}; - if (safe_commands.count(cmd) && argc < 4) - return die("Error: "+cmd+" requires a server name and service name. For safety, can't run on all services."); +// if (commands.count(cmd)) { +// std::set safe_commands = {"nuke", "fullnuke"}; +// if (safe_commands.count(cmd) && argc < 4) +// return die("Error: "+cmd+" requires a server name and service name. For safety, can't run on all services."); - // get all the services to run the command on. - ServerAndServices server_and_services; - if (!getCLIServices(safearg(argc, argv, 2), safearg(argc, argv, 3), server_and_services)) - return die("Error: "+cmd+" command requires server name and optionally service name"); +// // get all the services to run the command on. +// ServerAndServices server_and_services; +// if (!getCLIServices(safearg(argc, argv, 2), safearg(argc, argv, 3), server_and_services)) +// return die("Error: "+cmd+" command requires server name and optionally service name"); - // run the command on each service. - for (const auto& service_info : server_and_services.servicelist) { - if (!SIvalid(service_info)) - std::cerr<<"Error: Unable to get service information."< additional_args; - for (int i=4; i additional_args; +// for (int i=4; i +#include #include -#include "service_runner.hpp" // for ServiceStatus - namespace dropshell { // Server information structure diff --git a/source/src/service_runner.cpp b/source/src/service_runner.cpp index b40a18e..7196691 100644 --- a/source/src/service_runner.cpp +++ b/source/src/service_runner.cpp @@ -10,7 +10,6 @@ #include "utils/assert.hpp" #include "config.hpp" -#include "service_runner.hpp" #include "server_env_manager.hpp" #include "templates.hpp" #include "services.hpp" @@ -19,6 +18,75 @@ #include "command_registry.hpp" #include "shared_commands.hpp" + + + +namespace dropshell { + + + +class service_runner { + public: + service_runner(const std::string& server_name, const std::string& service_name); + bool isValid() const { return mValid; } + + // run a command over ssh, using the credentials from server.env (via server_env.hpp) + // first check that the command corresponds to a valid .sh file in the service directory + // then run the command, passing the {service_name}.env file as an argument + // do a lot of checks, such as: + // checking that we can ssh to the server. + // checking whether the service directory exists on the server. + // checking that the command exists in the service directory. + // checking that the command is a valid .sh file. + // checking that the {service_name}.env file exists in the service directory. + bool run_command(const std::string& command, std::vector additional_args={}, std::map env_vars={}); + + // check health of service. Silent. + // 1. run status.sh on the server + // 2. return the output of the status.sh script + + //HealthStatus is_healthy(); + + // std::string healthtick(); + // std::string healthmark(); + + public: + // backup and restore + bool backup(bool silent=false); + bool restore(std::string backup_file, bool silent=false); + + // nuke the service + bool nuke(bool silent=false); // nukes all data for this service on the remote server + bool fullnuke(); // nuke all data for this service on the remote server, and then nukes all the local service definitionfiles + + // launch an interactive ssh session on a server or service + // replaces the current dropshell process with the ssh process + bool interactive_ssh_service(); + + bool scp_file_to_remote(const std::string& local_path, const std::string& remote_path, bool silent=false); + bool scp_file_from_remote(const std::string& remote_path, const std::string& local_path, bool silent=false); + public: + // utility functions + static std::string get_latest_backup_file(const std::string& server, const std::string& service); + static bool interactive_ssh(const std::string & server_name, const std::string & command); + // static std::map get_all_services_status(std::string server_name); + + private: + std::string mServer; + server_env_manager mServerEnv; + LocalServiceInfo mServiceInfo; + std::string mService; + bool mValid; + + // Helper methods + public: +}; + + +} // namespace dropshell + + + namespace fs = std::filesystem; namespace dropshell { @@ -165,58 +233,6 @@ bool service_runner::run_command(const std::string& command, std::vector service_runner::get_all_services_status(std::string server_name) -{ - std::map status; - - server_env_manager env(server_name); - if (!env.is_valid()) { - std::cerr << "Error: Invalid server environment" << std::endl; - return status; - } - - std::string output; - if (!execute_ssh_command(env.get_SSH_INFO(), sCommand(remotepath::agent(server_name), "./_allservicesstatus.sh", { - {"HOST_NAME", server_name}, - {"SERVER",server_name}, - {"AGENT_PATH", remotepath::agent(server_name)} - }), cMode::CaptureOutput, &output)) - return status; - - std::stringstream ss(output); - std::string line; - while (std::getline(ss, line)) { - std::string key, value; - std::size_t pos = line.find("="); - if (pos != std::string::npos) { - key = dequote(trim(line.substr(0, pos))); - value = dequote(trim(line.substr(pos + 1))); - - // decode key, it's of format SERVICENAME_[HEALTH|PORTS] - std::string service_name = key.substr(0, key.find_last_of("_")); - std::string status_type = key.substr(key.find_last_of("_") + 1); - - if (status_type == "HEALTH") { // healthy|unhealthy|unknown - if (value == "healthy") - status[service_name].health = HealthStatus::HEALTHY; - else if (value == "unhealthy") - status[service_name].health = HealthStatus::UNHEALTHY; - else if (value == "unknown") - status[service_name].health = HealthStatus::UNKNOWN; - else - status[service_name].health = HealthStatus::ERROR; - } else if (status_type == "PORTS") { // port1,port2,port3 - std::vector ports = string2multi(value); - for (const auto& port : ports) { - if (port!="unknown") - status[service_name].ports.push_back(str2int(port)); - } - } - } - } - return status; -} - bool service_runner::interactive_ssh(const std::string & server_name, const std::string & command) { std::string serverpath = localpath::server(server_name); @@ -342,7 +358,7 @@ bool service_runner::restore(std::string backup_file, bool silent) return false; } - cRemoteTempFolder remote_temp_folder(mServerEnv); + shared_commands::cRemoteTempFolder remote_temp_folder(mServerEnv); mServerEnv.run_remote_template_command(mService, "restore", {}, silent, {{"BACKUP_FILE", remote_backup_file_path}, {"TEMP_DIR", remote_temp_folder.path()}}); } // dtor of remote_temp_folder will clean up the temp folder on the server @@ -449,7 +465,7 @@ bool service_runner::backup(bool silent) { ASSERT(3 == count_substring(magic_string, local_backup_file_path), "Invalid backup filename"); { // Run backup script - cRemoteTempFolder remote_temp_folder(mServerEnv); + shared_commands::cRemoteTempFolder remote_temp_folder(mServerEnv); if (!mServerEnv.run_remote_template_command(mService, command, {}, silent, {{"BACKUP_FILE", remote_backup_file_path}, {"TEMP_DIR", remote_temp_folder.path()}})) { std::cerr << "Backup script failed on remote server: " << remote_backup_file_path << std::endl; return false; @@ -469,27 +485,6 @@ bool service_runner::backup(bool silent) { return true; } -cRemoteTempFolder::cRemoteTempFolder(const server_env_manager &server_env) : mServerEnv(server_env) -{ - std::string p = remotepath::temp_files(server_env.get_server_name()) + "/" + random_alphanumeric_string(10); - std::string mkdir_cmd = "mkdir -p " + quote(p); - if (!execute_ssh_command(server_env.get_SSH_INFO(), sCommand("", mkdir_cmd, {}), cMode::Silent)) - std::cerr << "Failed to create temp directory on server" << std::endl; - else - mPath = p; -} - -cRemoteTempFolder::~cRemoteTempFolder() -{ - std::string rm_cmd = "rm -rf " + quote(mPath); - execute_ssh_command(mServerEnv.get_SSH_INFO(), sCommand("", rm_cmd, {}), cMode::Silent); -} - -std::string cRemoteTempFolder::path() const -{ - return mPath; -} - // Helper function to get the latest backup file for a given server and service std::string service_runner::get_latest_backup_file(const std::string& server, const std::string& service) { std::string local_backups_dir = gConfig().get_local_backup_path(); diff --git a/source/src/service_runner.hpp b/source/src/service_runner.hpp deleted file mode 100644 index 048c009..0000000 --- a/source/src/service_runner.hpp +++ /dev/null @@ -1,102 +0,0 @@ -// server_service.hpp -// -// manage a service on a server -// - -#ifndef SERVICE_RUNNER_HPP -#define SERVICE_RUNNER_HPP - -#include -#include -#include -#include "server_env_manager.hpp" -#include "services.hpp" -#include "utils/utils.hpp" -#include "utils/hash.hpp" - -namespace dropshell { - -typedef enum HealthStatus { - HEALTHY, - UNHEALTHY, - NOTINSTALLED, - ERROR, - UNKNOWN -} HealthStatus; - -typedef struct ServiceStatus { - HealthStatus health; - std::vector ports; -} ServiceStatus; - - -class service_runner { - public: - service_runner(const std::string& server_name, const std::string& service_name); - bool isValid() const { return mValid; } - - // run a command over ssh, using the credentials from server.env (via server_env.hpp) - // first check that the command corresponds to a valid .sh file in the service directory - // then run the command, passing the {service_name}.env file as an argument - // do a lot of checks, such as: - // checking that we can ssh to the server. - // checking whether the service directory exists on the server. - // checking that the command exists in the service directory. - // checking that the command is a valid .sh file. - // checking that the {service_name}.env file exists in the service directory. - bool run_command(const std::string& command, std::vector additional_args={}, std::map env_vars={}); - - // check health of service. Silent. - // 1. run status.sh on the server - // 2. return the output of the status.sh script - - HealthStatus is_healthy(); - - std::string healthtick(); - std::string healthmark(); - - public: - // backup and restore - bool backup(bool silent=false); - bool restore(std::string backup_file, bool silent=false); - - // nuke the service - bool nuke(bool silent=false); // nukes all data for this service on the remote server - bool fullnuke(); // nuke all data for this service on the remote server, and then nukes all the local service definitionfiles - - // launch an interactive ssh session on a server or service - // replaces the current dropshell process with the ssh process - bool interactive_ssh_service(); - - bool scp_file_to_remote(const std::string& local_path, const std::string& remote_path, bool silent=false); - bool scp_file_from_remote(const std::string& remote_path, const std::string& local_path, bool silent=false); - public: - // utility functions - static std::string get_latest_backup_file(const std::string& server, const std::string& service); - static bool interactive_ssh(const std::string & server_name, const std::string & command); - static std::map get_all_services_status(std::string server_name); - - private: - std::string mServer; - server_env_manager mServerEnv; - LocalServiceInfo mServiceInfo; - std::string mService; - bool mValid; - - // Helper methods - public: -}; - - class cRemoteTempFolder { - public: - cRemoteTempFolder(const server_env_manager & server_env); // create a temp folder on the remote server - ~cRemoteTempFolder(); // delete the temp folder on the remote server - std::string path() const; // get the path to the temp folder on the remote server - private: - std::string mPath; - const server_env_manager & mServerEnv; - }; - -} // namespace dropshell - -#endif // SERVICE_RUNNER_HPP diff --git a/source/src/services.cpp b/source/src/services.cpp index 14a1bab..11caccf 100644 --- a/source/src/services.cpp +++ b/source/src/services.cpp @@ -153,73 +153,6 @@ std::set list_backups(const std::string &server_name, const std::st return backups; } -bool create_service(const std::string &server_name, const std::string &template_name, const std::string &service_name, bool silent) -{ - if (server_name.empty() || template_name.empty() || service_name.empty()) - return false; - - std::string service_dir = localpath::service(server_name, service_name); - - if (service_dir.empty()) - { - if (!silent) - { - std::cerr << "Error: Couldn't locate server " << server_name << " in any config directory" << std::endl; - std::cerr << "Please check the server name is correct and try again" << std::endl; - std::cerr << "You can list all servers with 'dropshell servers'" << std::endl; - std::cerr << "You can create a new server with 'dropshell create-server " << server_name << "'" << std::endl; - } - return false; - } - - if (fs::exists(service_dir)) - { - if (!silent) - { - std::cerr << "Error: Service already exists: " << service_name << std::endl; - std::cerr << "Current service path: " << service_dir << std::endl; - } - return false; - } - - template_info tinfo = gTemplateManager().get_template_info(template_name); - if (!tinfo.is_set()) - { - if (!silent) - { - std::cerr << "Error: Template '" << template_name << "' not found" << std::endl; - std::cerr << "Please check the template name is correct and try again" << std::endl; - std::cerr << "You can list all templates with 'dropshell templates'" << std::endl; - std::cerr << "You can create a new template with 'dropshell create-template " << template_name << "'" << std::endl; - } - return false; - } - - // check template is all good. - if (!gTemplateManager().test_template(tinfo.local_template_path())) - { - if (!silent) - std::cerr << "Error: Template '" << template_name << "' is not valid" << std::endl; - return false; - } - - // create the service directory - fs::create_directory(service_dir); - - // copy the template config files to the service directory - recursive_copy(tinfo.local_template_path()/"config", service_dir); - - if (!silent) - { - std::cout << "Service " << service_name <<" created successfully"< & all_env_vars) diff --git a/source/src/services.hpp b/source/src/services.hpp index 447cc12..18651b7 100644 --- a/source/src/services.hpp +++ b/source/src/services.hpp @@ -27,8 +27,6 @@ namespace dropshell { // list all backups for a given service (across all servers) std::set list_backups(const std::string& server_name, const std::string& service_name); - - bool create_service(const std::string& server_name, const std::string& template_name, const std::string& service_name, bool silent=false); } // namespace dropshell #endif