mirror of
https://github.com/EvernodeXRPL/sashimono.git
synced 2026-04-29 15:38:00 +00:00
Sashimono CLI implementation (#38)
This commit is contained in:
@@ -18,6 +18,18 @@ add_executable(bootstrap_contract
|
||||
bootstrap-contract/bootstrap_contract.cpp
|
||||
)
|
||||
|
||||
#-------Sashi CLI-------
|
||||
|
||||
add_executable(sashi
|
||||
sashi-cli/cli-manager.cpp
|
||||
sashi-cli/main.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(sashi
|
||||
libboost_stacktrace_backtrace.a
|
||||
${CMAKE_DL_LIBS} # Needed for stacktrace support
|
||||
)
|
||||
|
||||
#-------Sashimono Agent-------
|
||||
|
||||
add_subdirectory(src/killswitch)
|
||||
@@ -47,6 +59,7 @@ target_link_libraries(sagent
|
||||
|
||||
add_dependencies(sagent
|
||||
bootstrap_contract
|
||||
sashi
|
||||
)
|
||||
|
||||
add_custom_command(TARGET sagent POST_BUILD
|
||||
@@ -63,7 +76,7 @@ target_precompile_headers(sagent PUBLIC src/pchheader.hpp)
|
||||
# Add target to generate the installer setup.
|
||||
add_custom_target(installer
|
||||
COMMAND mkdir -p ./build/sashimono-installer
|
||||
COMMAND bash -c "cp -r ./build/{sagent,hpfs,user-install.sh,user-uninstall.sh,contract_template} ./build/sashimono-installer/"
|
||||
COMMAND bash -c "cp -r ./build/{sagent,sashi,hpfs,user-install.sh,user-uninstall.sh,contract_template} ./build/sashimono-installer/"
|
||||
COMMAND bash -c "cp -r ./installer/{docker-install.sh,registry-install.sh,registry-uninstall.sh,sashimono-install.sh,sashimono-uninstall.sh} ./build/sashimono-installer/"
|
||||
COMMAND bash -c "cp -r ./dependencies/{user-cgcreate.sh,libblake3.so} ./build/sashimono-installer/"
|
||||
COMMAND tar cfz ./build/sashimono-installer.tar.gz --directory=./build/ sashimono-installer
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
const WebSocket = require('ws');
|
||||
const https = require('https');
|
||||
const http = require('http');
|
||||
const fs = require('fs');
|
||||
@@ -7,39 +6,30 @@ const { v4: uuidv4 } = require('uuid');
|
||||
const { execSync } = require("child_process");
|
||||
const express = require('express');
|
||||
|
||||
// Generate tls keys if not found.
|
||||
if (!fs.existsSync('./tlskey.pem')) {
|
||||
console.log("TLS key files not detected. Generating..");
|
||||
execSync("openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout tlskey.pem -out tlscert.pem -subj \"/C=SA/ST=SA/L=SA/O=SA/CN=SA\"");
|
||||
console.log("New tls key files generated.")
|
||||
const cliDevPath = "../../build/sashi";
|
||||
const cliProdPath = "/usr/bin/sashi";
|
||||
let cliPath;
|
||||
let args = process.argv;
|
||||
if (args.length == 3 && args[2] == 'prod')
|
||||
cliPath = cliProdPath;
|
||||
else if (args.length == 2 || (args.length == 3 && args[2] == 'dev'))
|
||||
cliPath = cliDevPath;
|
||||
else {
|
||||
console.log("Arguments mismatch.\n Usage: node message-board (optional)<dev|prod>");
|
||||
process.exit(0);
|
||||
}
|
||||
let restServer, websocketServer;
|
||||
const reqMap = {}; // Store response object vs message id to deliver response back when the SA responses.
|
||||
|
||||
if (!fs.existsSync(cliPath)) {
|
||||
console.error(`Sashi CLI does not exist in ${cliPath}.`)
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
let restServer;
|
||||
|
||||
/**
|
||||
* Interactive interface to get message from the command line and sent it to all the connected agents.
|
||||
*/
|
||||
const interatctiveInterface = async () => {
|
||||
const server = https.createServer({
|
||||
cert: fs.readFileSync('./tlscert.pem'),
|
||||
key: fs.readFileSync('./tlskey.pem')
|
||||
});
|
||||
|
||||
websocketServer = new WebSocket.Server({ server });
|
||||
|
||||
websocketServer.on('connection', (ws) => {
|
||||
ws.on('message', (msg) => {
|
||||
try {
|
||||
const message = JSON.parse(Buffer.from(msg).toString());
|
||||
console.log('Received: ', message);
|
||||
reqMap[message.reply_for] && reqMap[message.reply_for].status(message.type == "error" ? 500 : 200).send(message);
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error occured in json parsing." + error);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// start listening for stdin
|
||||
const rl = readLine.createInterface({
|
||||
input: process.stdin,
|
||||
@@ -49,9 +39,7 @@ const interatctiveInterface = async () => {
|
||||
// On ctrl + c we should close SA connection gracefully.
|
||||
rl.on('SIGINT', () => {
|
||||
console.log('SIGINT received...');
|
||||
websocketServer.close();
|
||||
rl.close();
|
||||
server.close();
|
||||
restServer && restServer.close();
|
||||
});
|
||||
|
||||
@@ -63,135 +51,149 @@ const interatctiveInterface = async () => {
|
||||
})
|
||||
}
|
||||
|
||||
server.listen(5000, () => {
|
||||
console.log(`wss://localhost:${server.address().port}`)
|
||||
console.log("Ready to accept inputs.");
|
||||
console.log("Ready to accept inputs.");
|
||||
|
||||
const inputPump = () => {
|
||||
rl.question('', async (inp) => {
|
||||
|
||||
if (inp.length > 0) {
|
||||
if (websocketServer.clients.size == 0) {
|
||||
console.log('No Sashimano agents connected yet.')
|
||||
}
|
||||
else {
|
||||
switch (inp) {
|
||||
case 'create':
|
||||
contractId = await askForInput('Contract ID (default:uuidv4)', uuidv4());
|
||||
image = await askForInput('Image: 1=ubuntu(default) | 2=nodejs', "1");
|
||||
if (image != "1" && image != "2") {
|
||||
console.error('Invalid image. (Should be "1" or "2").')
|
||||
break;
|
||||
}
|
||||
|
||||
sendToAllAgents(JSON.stringify({
|
||||
id: uuidv4(),
|
||||
type: 'create',
|
||||
owner_pubkey: 'ed5cb83404120ac759609819591ef839b7d222c84f1f08b3012f490586159d2b50',
|
||||
contract_id: contractId,
|
||||
image: (image == "1" ? "ubt.20.04" : "ubt.20.04-njs.14")
|
||||
}));
|
||||
break;
|
||||
case 'initiate':
|
||||
containerName = await askForInput('Container Name');
|
||||
role = await askForInput('Role: validator(default) | observer', "validator");
|
||||
if (role != 'validator' && role != 'observer') {
|
||||
console.error('Invalid role. (Should be "validator" or "observer").')
|
||||
break;
|
||||
}
|
||||
|
||||
history = await askForInput('History <{full|custom},max_primary_shards,max_raw_shards> (custom,1,1)', "custom,1,1");
|
||||
split = [];
|
||||
if (history) {
|
||||
split = history.split(',');
|
||||
if (split.length == 0 || split.length == 0 > 3) {
|
||||
console.error('Invalid history.')
|
||||
break;
|
||||
}
|
||||
else if (split[0] != 'full' && split[0] != 'custom') {
|
||||
console.error('Invalid history. (Should be "full" or "custom").')
|
||||
break;
|
||||
}
|
||||
}
|
||||
peers = await askForInput('Comma seperated Peer List <host1:port1>,<host2:port2>,...');
|
||||
unl = await askForInput('Comma seperated UNL <pubkey1>,<pubkey2>,...');
|
||||
sendToAllAgents(JSON.stringify({
|
||||
id: uuidv4(),
|
||||
type: 'initiate',
|
||||
container_name: containerName,
|
||||
peers: peers ? peers.split(',') : [],
|
||||
unl: unl ? unl.split(',') : [],
|
||||
role: role,
|
||||
history: split.length > 0 ? split[0] : '',
|
||||
max_primary_shards: split.length > 1 ? parseInt(split[1]) : '',
|
||||
max_raw_shards: split.length > 2 ? parseInt(split[2]) : ''
|
||||
}));
|
||||
break;
|
||||
case 'destroy':
|
||||
containerName = await askForInput('Container Name');
|
||||
sendToAllAgents(JSON.stringify({
|
||||
id: uuidv4(),
|
||||
type: 'destroy',
|
||||
container_name: containerName
|
||||
}))
|
||||
break;
|
||||
case 'start':
|
||||
containerName = await askForInput('Container Name');
|
||||
sendToAllAgents(JSON.stringify({
|
||||
id: uuidv4(),
|
||||
type: 'start',
|
||||
container_name: containerName
|
||||
}))
|
||||
break;
|
||||
case 'stop':
|
||||
containerName = await askForInput('Container Name');
|
||||
sendToAllAgents(JSON.stringify({
|
||||
id: uuidv4(),
|
||||
type: 'stop',
|
||||
container_name: containerName
|
||||
}))
|
||||
break;
|
||||
|
||||
default:
|
||||
console.error('Invalid command. Only valid [create, initiate, destroy, start and stop]');
|
||||
break;
|
||||
const inputPump = () => {
|
||||
rl.question('', async (inp) => {
|
||||
if (inp.length > 0) {
|
||||
switch (inp) {
|
||||
case 'status':
|
||||
checkAgentStatus();
|
||||
break;
|
||||
case 'create':
|
||||
contractId = await askForInput('Contract ID (default:uuidv4)', uuidv4());
|
||||
image = await askForInput('Image: 1=ubuntu(default) | 2=nodejs', "1");
|
||||
if (image != "1" && image != "2") {
|
||||
console.error('Invalid image. (Should be "1" or "2").')
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
sendToAgent(JSON.stringify({
|
||||
id: uuidv4(),
|
||||
type: 'create',
|
||||
owner_pubkey: 'ed5cb83404120ac759609819591ef839b7d222c84f1f08b3012f490586159d2b50',
|
||||
contract_id: contractId,
|
||||
image: (image == "1" ? "ubt.20.04" : "ubt.20.04-njs.14")
|
||||
}));
|
||||
break;
|
||||
case 'initiate':
|
||||
containerName = await askForInput('Container Name');
|
||||
role = await askForInput('Role: validator(default) | observer', "validator");
|
||||
if (role != 'validator' && role != 'observer') {
|
||||
console.error('Invalid role. (Should be "validator" or "observer").')
|
||||
break;
|
||||
}
|
||||
|
||||
history = await askForInput('History <{full|custom},max_primary_shards,max_raw_shards> (custom,1,1)', "custom,1,1");
|
||||
split = [];
|
||||
if (history) {
|
||||
split = history.split(',');
|
||||
if (split.length == 0 || split.length == 0 > 3) {
|
||||
console.error('Invalid history.')
|
||||
break;
|
||||
}
|
||||
else if (split[0] != 'full' && split[0] != 'custom') {
|
||||
console.error('Invalid history. (Should be "full" or "custom").')
|
||||
break;
|
||||
}
|
||||
}
|
||||
peers = await askForInput('Comma seperated Peer List <host1:port1>,<host2:port2>,...');
|
||||
unl = await askForInput('Comma seperated UNL <pubkey1>,<pubkey2>,...');
|
||||
sendToAgent(JSON.stringify({
|
||||
id: uuidv4(),
|
||||
type: 'initiate',
|
||||
container_name: containerName,
|
||||
peers: peers ? peers.split(',') : [],
|
||||
unl: unl ? unl.split(',') : [],
|
||||
role: role,
|
||||
history: split.length > 0 ? split[0] : '',
|
||||
max_primary_shards: split.length > 1 ? parseInt(split[1]) : '',
|
||||
max_raw_shards: split.length > 2 ? parseInt(split[2]) : ''
|
||||
}));
|
||||
break;
|
||||
case 'destroy':
|
||||
containerName = await askForInput('Container Name');
|
||||
sendToAgent(JSON.stringify({
|
||||
id: uuidv4(),
|
||||
type: 'destroy',
|
||||
container_name: containerName
|
||||
}))
|
||||
break;
|
||||
case 'start':
|
||||
containerName = await askForInput('Container Name');
|
||||
sendToAgent(JSON.stringify({
|
||||
id: uuidv4(),
|
||||
type: 'start',
|
||||
container_name: containerName
|
||||
}))
|
||||
break;
|
||||
case 'stop':
|
||||
containerName = await askForInput('Container Name');
|
||||
sendToAgent(JSON.stringify({
|
||||
id: uuidv4(),
|
||||
type: 'stop',
|
||||
container_name: containerName
|
||||
}))
|
||||
break;
|
||||
|
||||
default:
|
||||
console.error('Invalid command. Only valid [create, initiate, destroy, start and stop]');
|
||||
break;
|
||||
}
|
||||
|
||||
inputPump();
|
||||
})
|
||||
}
|
||||
inputPump();
|
||||
});
|
||||
}
|
||||
|
||||
inputPump();
|
||||
})
|
||||
}
|
||||
inputPump();
|
||||
}
|
||||
|
||||
const sendToAllAgents = (msg) => {
|
||||
websocketServer && websocketServer.clients.forEach(ws => {
|
||||
ws.send(msg);
|
||||
});
|
||||
const sendToAgent = (msg, res = null) => {
|
||||
try {
|
||||
let output = execSync(`${cliPath} json '${msg}'`, { stdio: 'pipe' });
|
||||
let message = Buffer.from(output).toString();
|
||||
message = JSON.parse(message.substring(0, message.length - 2)); // Skipping the \n from the result.
|
||||
console.log('Received: ', message);
|
||||
res && res.status((message.content && typeof message.content == 'string' && message.content.endsWith("error")) ? 500 : 200).send(message);
|
||||
}
|
||||
catch (e) {
|
||||
console.error(`Message sending error. ${e}`);
|
||||
res && res.status(500).send(`Message sending error. ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
const checkAgentStatus = (res = null) => {
|
||||
try {
|
||||
let output = execSync(`${cliPath} status`, { stdio: 'pipe' });
|
||||
let message = Buffer.from(output).toString();
|
||||
message = message.substring(0, message.length - 1); // Skipping the \n from the result.
|
||||
console.log(`Socket ${message} is online.`);
|
||||
res && res.status(200).send(message);
|
||||
return true;
|
||||
}
|
||||
catch (e) {
|
||||
console.error(`Socket is offline. ${e}`);
|
||||
res && res.status(500).send(`Socket is offline. ${e}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const webServerProtocol = (process.env.SSL === "true") ? "https" : "http";
|
||||
const webServerPort = 5001;
|
||||
|
||||
const restApi = async () => {
|
||||
// Generate tls keys if not found.
|
||||
if (webServerProtocol == "https" && !fs.existsSync('./tlskey.pem')) {
|
||||
console.log("TLS key files not detected. Generating..");
|
||||
execSync("openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout tlskey.pem -out tlscert.pem -subj \"/C=SA/ST=SA/L=SA/O=SA/CN=SA\"");
|
||||
console.log("New tls key files generated.")
|
||||
}
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
// Handle errors before forward to processing.
|
||||
app.use((req, res, next) => {
|
||||
if (websocketServer.clients.size == 0) {
|
||||
console.log('No Sashimano agents connected yet.')
|
||||
res.status(404).send('No Sashimano agents connected yet.');
|
||||
}
|
||||
else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
app.post("/status", (req, res) => {
|
||||
res.send("Message board running...");
|
||||
checkAgentStatus(res);
|
||||
});
|
||||
app.post("/create", (req, res) => {
|
||||
const id = uuidv4();
|
||||
@@ -202,58 +204,49 @@ const restApi = async () => {
|
||||
contract_id: (req.body.contract_id === "") ? uuidv4() : req.body.contract_id,
|
||||
image: req.body.image ? req.body.image : "ubt.20.04"
|
||||
};
|
||||
reqMap[id] = res;
|
||||
sendToAllAgents(JSON.stringify(msg));
|
||||
sendToAgent(JSON.stringify(msg), res);
|
||||
});
|
||||
app.post("/initiate", (req, res) => {
|
||||
const id = uuidv4();
|
||||
const msg = {
|
||||
id,
|
||||
type: 'initiate',
|
||||
owner_pubkey: req.body.owner_pubkey,
|
||||
container_name: req.body.container_name,
|
||||
peers: req.body.peers ? req.body.peers: [],
|
||||
unl: req.body.unl ? req.body.unl: [],
|
||||
role: req.body.role? req.body.role: 'validator',
|
||||
history: req.body.history? req.body.history: 'custom',
|
||||
max_primary_shards: req.body.max_primary_shards? req.body.max_primary_shards: 1,
|
||||
max_raw_shards: req.body.max_raw_shards? req.body.max_raw_shards: 1
|
||||
peers: req.body.peers ? req.body.peers : [],
|
||||
unl: req.body.unl ? req.body.unl : [],
|
||||
role: req.body.role ? req.body.role : 'validator',
|
||||
history: req.body.history ? req.body.history : 'custom',
|
||||
max_primary_shards: req.body.max_primary_shards ? req.body.max_primary_shards : 1,
|
||||
max_raw_shards: req.body.max_raw_shards ? req.body.max_raw_shards : 1
|
||||
};
|
||||
reqMap[id] = res;
|
||||
sendToAllAgents(JSON.stringify(msg));
|
||||
sendToAgent(JSON.stringify(msg), res);
|
||||
});
|
||||
app.post("/start", (req, res) => {
|
||||
const id = uuidv4();
|
||||
const msg = {
|
||||
id,
|
||||
type: 'start',
|
||||
owner_pubkey: req.body.owner_pubkey,
|
||||
container_name: req.body.container_name
|
||||
};
|
||||
reqMap[id] = res;
|
||||
sendToAllAgents(JSON.stringify(msg));
|
||||
sendToAgent(JSON.stringify(msg), res);
|
||||
});
|
||||
app.post("/stop", (req, res) => {
|
||||
const id = uuidv4();
|
||||
const msg = {
|
||||
id,
|
||||
type: 'stop',
|
||||
owner_pubkey: req.body.owner_pubkey,
|
||||
container_name: req.body.container_name
|
||||
};
|
||||
reqMap[id] = res;
|
||||
sendToAllAgents(JSON.stringify(msg));
|
||||
sendToAgent(JSON.stringify(msg), res);
|
||||
});
|
||||
app.post("/destroy", (req, res) => {
|
||||
const id = uuidv4();
|
||||
const msg = {
|
||||
id,
|
||||
type: 'destroy',
|
||||
owner_pubkey: req.body.owner_pubkey,
|
||||
container_name: req.body.container_name
|
||||
};
|
||||
reqMap[id] = res;
|
||||
sendToAllAgents(JSON.stringify(msg));
|
||||
sendToAgent(JSON.stringify(msg), res);
|
||||
});
|
||||
restServer = (webServerProtocol == "https") ?
|
||||
https.createServer({
|
||||
@@ -266,6 +259,8 @@ const restApi = async () => {
|
||||
}
|
||||
|
||||
(async () => {
|
||||
await restApi();
|
||||
await interatctiveInterface();
|
||||
if (checkAgentStatus()) {
|
||||
await restApi();
|
||||
await interatctiveInterface();
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# Sashimono agent installation script.
|
||||
# This must be executed with root privileges.
|
||||
|
||||
user_bin=/usr/bin
|
||||
sashimono_bin=/usr/bin/sashimono-agent
|
||||
docker_bin=/usr/bin/sashimono-agent/dockerbin
|
||||
sashimono_data=/etc/sashimono
|
||||
@@ -62,6 +63,9 @@ function rollback() {
|
||||
cp "$script_dir"/{sagent,hpfs,user-cgcreate.sh,user-install.sh,user-uninstall.sh} $sashimono_bin
|
||||
chmod -R +x $sashimono_bin
|
||||
|
||||
# Install Sashimono CLI binaries into user bin dir.
|
||||
cp "$script_dir"/sashi $user_bin
|
||||
|
||||
# Download and install rootless dockerd.
|
||||
"$script_dir"/docker-install.sh $docker_bin
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# Sashimono agent uninstall script.
|
||||
# -q for non-interactive (quiet) mode
|
||||
|
||||
user_bin=/usr/bin
|
||||
sashimono_bin=/usr/bin/sashimono-agent
|
||||
docker_bin=/usr/bin/sashimono-agent/dockerbin
|
||||
sashimono_data=/etc/sashimono
|
||||
@@ -74,6 +75,9 @@ rm /etc/systemd/system/$sashimono_service.service
|
||||
echo "Deleting binaries..."
|
||||
rm -r $sashimono_bin
|
||||
|
||||
echo "Deleting Sashimono CLI..."
|
||||
rm $user_bin/sashi
|
||||
|
||||
echo "Deleting data folder..."
|
||||
rm -r $sashimono_data
|
||||
|
||||
|
||||
143
sashi-cli/cli-manager.cpp
Normal file
143
sashi-cli/cli-manager.cpp
Normal file
@@ -0,0 +1,143 @@
|
||||
#include "pchheader.hpp"
|
||||
#include "cli-manager.hpp"
|
||||
|
||||
namespace cli
|
||||
{
|
||||
constexpr const char *SOCKET_NAME = "sa.sock"; // Name of the sashimono socket.
|
||||
constexpr const char *DATA_DIR = "/etc/sashimono"; // Sashimono data directory.
|
||||
constexpr const int BUFFER_SIZE = 1024; // Max read buffer size.
|
||||
|
||||
cli_context ctx;
|
||||
|
||||
bool init_success = false;
|
||||
|
||||
/**
|
||||
* Initialize the socket and connect.
|
||||
* @return 0 on success, -1 on error.
|
||||
*/
|
||||
int init(std::string_view sashi_dir)
|
||||
{
|
||||
ctx.sashi_dir = sashi_dir;
|
||||
|
||||
// Get the socket path from available location.
|
||||
if (get_socket_path(ctx.socket_path) == -1)
|
||||
return -1;
|
||||
|
||||
// Create the seq paket socket.
|
||||
ctx.socket_fd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
|
||||
if (ctx.socket_fd == -1)
|
||||
{
|
||||
std::cerr << errno << " :Error while creating the sashimono socket.\n";
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct sockaddr_un addr;
|
||||
memset(&addr, 0, sizeof(struct sockaddr_un));
|
||||
|
||||
addr.sun_family = AF_UNIX;
|
||||
strncpy(addr.sun_path, ctx.socket_path.data(), sizeof(addr.sun_path) - 1);
|
||||
|
||||
if (connect(ctx.socket_fd, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)) == -1)
|
||||
{
|
||||
// If permission denied, show a custom error.
|
||||
if (errno == EACCES)
|
||||
std::cerr << "Permission denied: Only root or users in 'sashiadmin' group can access the sashimono socket.\n";
|
||||
else
|
||||
std::cerr << errno << " :Error while connecting to the sashimono socket.\n";
|
||||
close(ctx.socket_fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
init_success = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locate and return the sashimono agent socket path according predefined rules.
|
||||
* If sa.sock found on the same path as the cli binary, use that. (to support dev testing)
|
||||
* Else sa.sock found on /etc/sashimono, use that.
|
||||
* Else show error.
|
||||
* @param socket_path Socket path to be populated.
|
||||
* @return 0 on success, -1 on error.
|
||||
*/
|
||||
int get_socket_path(std::string &socket_path)
|
||||
{
|
||||
// Check whether socket exists in exec path.
|
||||
std::string path = ctx.sashi_dir + std::string("/") + SOCKET_NAME;
|
||||
struct stat st;
|
||||
if (stat(path.data(), &st) == 0 && S_ISSOCK(st.st_mode))
|
||||
{
|
||||
socket_path = path;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Otherwise check in the data dir.
|
||||
path = DATA_DIR + std::string("/") + SOCKET_NAME;
|
||||
memset(&st, 0, sizeof(struct stat));
|
||||
if (stat(path.data(), &st) == 0 && S_ISSOCK(st.st_mode))
|
||||
{
|
||||
socket_path = path;
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::cerr << SOCKET_NAME << " is not found.\n";
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a given message into the sashimono socket.
|
||||
* @param message Message to be write.
|
||||
* @return 0 on success, -1 on error.
|
||||
*/
|
||||
int write_to_socket(std::string_view message)
|
||||
{
|
||||
if (!init_success)
|
||||
{
|
||||
std::cerr << "Sashimono socket is not initialized.\n";
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (write(ctx.socket_fd, message.data(), message.size()) == -1)
|
||||
{
|
||||
std::cerr << errno << " :Error while wrting to the sashimono socket.\n";
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read message from the sashimono socket.
|
||||
* @param message Message to be read.
|
||||
* @return Read message length on success, -1 on error.
|
||||
*/
|
||||
int read_from_socket(std::string &message)
|
||||
{
|
||||
if (!init_success)
|
||||
{
|
||||
std::cerr << "Sashimono socket is not initialized.\n";
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Resize the message to max length and resize to original read length after reading.
|
||||
message.resize(BUFFER_SIZE);
|
||||
const int res = read(ctx.socket_fd, message.data(), message.length());
|
||||
if (res == -1)
|
||||
{
|
||||
std::cerr << errno << " :Error while reading from the sashimono socket.\n";
|
||||
return -1;
|
||||
}
|
||||
message.resize(res);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the socket and deinitialize.
|
||||
*/
|
||||
void deinit()
|
||||
{
|
||||
if (init_success)
|
||||
close(ctx.socket_fd);
|
||||
}
|
||||
}
|
||||
26
sashi-cli/cli-manager.hpp
Normal file
26
sashi-cli/cli-manager.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#ifndef _CLI_MANAGER_
|
||||
#define _CLI_MANAGER_
|
||||
|
||||
namespace cli
|
||||
{
|
||||
struct cli_context
|
||||
{
|
||||
std::string sashi_dir; // Path of the Sashi CLI executable.
|
||||
std::string socket_path; // Path of the sashimono socket.
|
||||
int socket_fd = -1; // File descriptor of the socket.
|
||||
};
|
||||
|
||||
extern cli_context ctx;
|
||||
|
||||
int init(std::string_view sashi_dir);
|
||||
|
||||
int get_socket_path(std::string &socket_path);
|
||||
|
||||
int write_to_socket(std::string_view message);
|
||||
|
||||
int read_from_socket(std::string &message);
|
||||
|
||||
void deinit();
|
||||
}
|
||||
|
||||
#endif
|
||||
113
sashi-cli/main.cpp
Normal file
113
sashi-cli/main.cpp
Normal file
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
Entry point for Sashi-cli
|
||||
**/
|
||||
#include "pchheader.hpp"
|
||||
#include "cli-manager.hpp"
|
||||
|
||||
#define PARSE_ERROR \
|
||||
{ \
|
||||
std::cerr << "Arguments mismatch.\n"; \
|
||||
std::cerr << "Usage:\n"; \
|
||||
std::cerr << "sashi status\n"; \
|
||||
std::cerr << "sashi json <json message>\n"; \
|
||||
std::cerr << "Example: sashi json '{\"container_name\":\"<container name>\", ...}'\n"; \
|
||||
return -1; \
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (argc > 1)
|
||||
{
|
||||
// Take the realpath of sash exec path.
|
||||
std::array<char, PATH_MAX> buffer;
|
||||
::realpath(argv[0], buffer.data());
|
||||
buffer[PATH_MAX] = '\0';
|
||||
const std::string exec_dir = dirname(buffer.data());
|
||||
|
||||
const std::string command = argv[1];
|
||||
|
||||
if (command == "status")
|
||||
{
|
||||
if (cli::init(exec_dir) == -1)
|
||||
return -1;
|
||||
|
||||
std::cout << cli::ctx.socket_path << std::endl;
|
||||
cli::deinit();
|
||||
return 0;
|
||||
}
|
||||
else if (command == "json" && argc == 3)
|
||||
{
|
||||
std::string output;
|
||||
if (cli::init(exec_dir) == -1 || cli::write_to_socket(argv[2]) == -1 || cli::read_from_socket(output) == -1)
|
||||
{
|
||||
cli::deinit();
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::cout << output << std::endl;
|
||||
cli::deinit();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
PARSE_ERROR
|
||||
}
|
||||
13
sashi-cli/pchheader.hpp
Normal file
13
sashi-cli/pchheader.hpp
Normal file
@@ -0,0 +1,13 @@
|
||||
#ifndef _CLI_PCHHEADER_
|
||||
#define _CLI_PCHHEADER_
|
||||
|
||||
#include <boost/stacktrace.hpp>
|
||||
#include <csignal>
|
||||
#include <iostream>
|
||||
#include <libgen.h>
|
||||
#include <string>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#endif
|
||||
@@ -260,14 +260,15 @@ namespace comm
|
||||
**/
|
||||
int read_socket(std::string &message)
|
||||
{
|
||||
char buffer[BUFFER_SIZE];
|
||||
const int ret = read(ctx.data_socket, buffer, BUFFER_SIZE);
|
||||
// Resize the message to max length and resize to original read length after reading.
|
||||
message.resize(BUFFER_SIZE);
|
||||
const int ret = read(ctx.data_socket, message.data(), message.length());
|
||||
if (ret == -1)
|
||||
{
|
||||
LOG_ERROR << errno << ": Error receiving data.";
|
||||
return -1;
|
||||
}
|
||||
message = std::string(buffer);
|
||||
message.resize(ret);
|
||||
return ret;
|
||||
}
|
||||
} // namespace comm
|
||||
|
||||
Reference in New Issue
Block a user