Files
sashimono/sashi-cli/main.cpp
2024-05-14 15:39:29 +05:30

240 lines
7.7 KiB
C++

/**
Entry point for Sashi-cli
**/
#include "pchheader.hpp"
#include "cli-manager.hpp"
#include "version.hpp"
#define DEV_MODE "DEV_MODE"
#define DATA_DIR "DATA_DIR"
std::string data_dir;
/**
* Performs any cleanup on graceful application termination.
*/
void deinit()
{
cli::deinit();
}
void segfault_handler(int signum)
{
std::cout << boost::stacktrace::stacktrace() << std::endl;
exit(SIGABRT);
}
/**
* Global exception handler for std exceptions.
*/
void std_terminate() noexcept
{
const std::exception_ptr exptr = std::current_exception();
if (exptr != 0)
{
try
{
std::rethrow_exception(exptr);
}
catch (std::exception &ex)
{
std::cerr << "std error: " << ex.what() << std::endl;
}
catch (...)
{
std::cerr << "std error: Terminated due to unknown exception\n";
}
}
else
{
std::cerr << "std error: Terminated due to unknown reason\n";
}
std::cerr << boost::stacktrace::stacktrace() << std::endl;
exit(1);
}
template <typename executor>
int execute_cli(executor const &func)
{
if (cli::init(data_dir) == -1)
return -1;
const int ret = func();
cli::deinit();
return ret;
}
/**
* Parses CLI args and extracts sashimono agent command and parameters given using CLI11 library.
* @param argc Argument count.
* @param argv Arguments.
* @returns 0 on success, -1 on error.
*/
int parse_cmd(int argc, char **argv)
{
// Initialize CLI.
CLI::App app("Sashimono CLI");
app.set_help_all_flag("--help-all", "Expand all help");
// Initialize subcommands.
CLI::App *version = app.add_subcommand("version", "Displays Sashimono CLI version.");
CLI::App *status = app.add_subcommand("status", "Check socket accessibility.");
CLI::App *list = app.add_subcommand("list", "List all instances.");
CLI::App *json = app.add_subcommand("json", "JSON payload. Example: sashi json -m '{\"type\":\"<instruction_type>\", ...}'");
CLI::App *create = app.add_subcommand("create", "Creates an instance.");
CLI::App *start = app.add_subcommand("start", "Starts an instance.");
CLI::App *stop = app.add_subcommand("stop", "Stops an instance.");
CLI::App *destroy = app.add_subcommand("destroy", "Destroys an instance.");
CLI::App *attach = app.add_subcommand("attach", "Attachs to the bash of a instance.");
// Initialize options.
std::string json_message;
json->add_option("-m,--message", json_message, "JSON message");
create->group(""); // Hides 'create' command from help-all
std::string owner, contract_id, image, outbound_ipv6, outbound_net_interface;
create->add_option("-o,--owner", owner, "Hex (ed-prefixed) public key of the instance owner");
create->add_option("-c,--contract-id", contract_id, "Contract Id (GUID) of the instance");
create->add_option("-i,--image", image, "Container image to use");
create->add_option("-a,--outbound-ipv6", outbound_ipv6, "Outbound IPV6 Address");
create->add_option("-f,--outbound-net-interface", outbound_net_interface, "Outbound IPV6 Network Interface (Façade)");
std::string container_name;
create->add_option("-n,--name", container_name, "Instance name");
start->add_option("-n,--name", container_name, "Instance name");
stop->add_option("-n,--name", container_name, "Instance name");
destroy->add_option("-n,--name", container_name, "Instance name");
attach->add_option("-n,--name", container_name, "Instance name");
CLI11_PARSE(app, argc, argv);
// Take the realpath of sashi cli exec path.
{
std::array<char, PATH_MAX> buffer;
const char *env_var = getenv(DATA_DIR);
if (env_var != nullptr)
{
data_dir = std::string(env_var);
}
else if (realpath(argv[0], buffer.data()))
{
data_dir = dirname(buffer.data());
}
else
{
// If real path fails, we get the current dir as exec bin path.
if (!getcwd(buffer.data(), buffer.size()))
{
std::cerr << errno << ": Error in executable path." << std::endl;
return -1;
}
data_dir = buffer.data();
}
}
// Verifying subcommands.
bool is_dev_mode = (getenv(DEV_MODE) != nullptr);
if (!is_dev_mode)
{
if (create->parsed())
{
std::cout << "Command not supported: Run with --help or --help-all for more information." << std::endl;
return -1;
}
if (json->parsed() && !json_message.empty())
{
jsoncons::json json_data = jsoncons::json::parse(json_message);
if (json_data.contains("type") && json_data["type"].as_string() == "create")
{
std::cout << "Command not supported: Run with --help or --help-all for more information." << std::endl;
return -1;
}
}
}
if (version->parsed())
{
std::cout << "Sashimono CLI version " << version::CLI_VERSION << std::endl;
return 0;
}
else if (status->parsed())
{
return execute_cli([]()
{
std::cout << cli::ctx.socket_path << std::endl;
return 0; });
}
else if (list->parsed())
{
return execute_cli([]()
{
if (cli::list() == -1)
{
std::cerr << "Failed to list instances." << std::endl;
return -1;
}
return 0; });
}
else if (json->parsed() && !json_message.empty())
{
return execute_cli([&]()
{
std::string output;
if (cli::get_json_output(json_message, output) == -1)
return -1;
std::cout << output << std::endl;
return 0; });
}
else if (create->parsed() && !container_name.empty() && !owner.empty() && !contract_id.empty() && !image.empty())
{
return execute_cli([&]()
{ return cli::create(container_name, owner, contract_id, image, outbound_ipv6, outbound_net_interface); });
}
else if (start->parsed() && !container_name.empty())
{
return execute_cli([&]()
{ return cli::execute_basic("start", container_name); });
}
else if (stop->parsed() && !container_name.empty())
{
return execute_cli([&]()
{ return cli::execute_basic("stop", container_name); });
}
else if (destroy->parsed() && !container_name.empty())
{
return execute_cli([&]()
{ return cli::execute_basic("destroy", container_name); });
}
else if (attach->parsed() && !container_name.empty())
{
return execute_cli([&]()
{ return cli::docker_exec("attach", container_name); });
}
std::cout << app.help();
return -1;
}
int main(int argc, char **argv)
{
// Register exception and segfault handlers.
std::set_terminate(&std_terminate);
signal(SIGSEGV, &segfault_handler);
signal(SIGABRT, &segfault_handler);
// Disable SIGPIPE to avoid crashing on broken pipe IO.
{
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGPIPE);
// sigprocmask is used instead of pthread_sigmask since this is single threaded.
sigprocmask(SIG_BLOCK, &mask, NULL);
}
return parse_cmd(argc, argv);
}