Integrating runner.
Some checks failed
Dropshell Test / Build_and_Test (push) Failing after 23s

This commit is contained in:
Your Name 2025-05-10 15:24:53 +12:00
parent ec5f4ad38d
commit 35c97728c9
15 changed files with 96 additions and 26403 deletions

28
runner/.gitignore vendored
View File

@ -1,28 +0,0 @@
# Build directory
build/
cmake-build-*/
# IDE files
.idea/
.vscode/
*.swp
*~
# Compiled object files
*.o
*.obj
# Compiled dynamic libraries
*.so
*.dylib
*.dll
# Executables
*.exe
runner
# CMake generated files
CMakeCache.txt
CMakeFiles/
cmake_install.cmake
Makefile

View File

@ -1,17 +0,0 @@
cmake_minimum_required(VERSION 3.10)
project(runner_demo)
set(CMAKE_CXX_STANDARD 17)
find_package(OpenSSL REQUIRED)
find_package(PkgConfig REQUIRED)
pkg_check_modules(LIBSSH REQUIRED libssh)
include_directories(${LIBSSH_INCLUDE_DIRS})
link_directories(${LIBSSH_LIBRARY_DIRS})
add_library(runner_lib runner.cpp)
target_include_directories(runner_lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(runner_lib ${LIBSSH_LIBRARIES})
add_executable(runner runner_demo.cpp)
target_link_libraries(runner runner_lib OpenSSL::SSL OpenSSL::Crypto ${LIBSSH_LIBRARIES})

View File

@ -1,42 +0,0 @@
# Runner
Simple c++ demonstration program of the dropshell runner library.
use:
runner
The exit code is that of the command run, or -1 if the command couldn't be run.
The c++ library used, which is contained in this codebase, has one simple function:
typedef struct sSSHInfo {
std::string host;
std::string user;
std::string port;
} sSSHInfo;
static int execute_cmd(
const std::string& command,
const std::vector<std::string>& args,
const std::string& working_dir,
const std::map<std::string, std::string>& env,
bool silent,
bool interactive,
sSSHInfo * sshinfo = nullptr,
std::string* output = nullptr,
);
If SSH information is provided, the command will be executed on the remote server. Otherwise on the local machine.
If output is provided, the output of the command is captured.
If interactive is true, then an interactive session is created - e.g. for running nano to remotely edit a file, or sshing into a docker container from the remote host.
If silent is true, then all output is suppressed.
Before the command is run, the current directory is changed to working_dir on the remote host (or local host if no ssh info provided).

View File

@ -1,34 +0,0 @@
#!/bin/bash
set -e
# Check for cmake
if ! command -v cmake &> /dev/null; then
echo "Error: cmake is not installed. Please install cmake." >&2
exit 1
fi
# Check for g++
if ! command -v g++ &> /dev/null; then
echo "Error: g++ is not installed. Please install g++." >&2
exit 1
fi
# Check for OpenSSL
if ! pkg-config --exists openssl; then
echo "Error: OpenSSL development libraries not found. Please install libssl-dev." >&2
exit 1
fi
BUILD_DIR=build
mkdir -p "$BUILD_DIR"
cd "$BUILD_DIR"
cmake ..
make -j$(nproc)
if [ -f runner ]; then
echo "Build successful. Run ./build/runner"
else
echo "Build failed. Check the output above for errors."
exit 1
fi

File diff suppressed because it is too large Load Diff

View File

@ -1,187 +0,0 @@
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_
#define INCLUDE_NLOHMANN_JSON_FWD_HPP_
#include <cstdint> // int64_t, uint64_t
#include <map> // map
#include <memory> // allocator
#include <string> // string
#include <vector> // vector
// #include <nlohmann/detail/abi_macros.hpp>
// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
// This file contains all macro definitions affecting or depending on the ABI
#ifndef JSON_SKIP_LIBRARY_VERSION_CHECK
#if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH)
#if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 12 || NLOHMANN_JSON_VERSION_PATCH != 0
#warning "Already included a different version of the library!"
#endif
#endif
#endif
#define NLOHMANN_JSON_VERSION_MAJOR 3 // NOLINT(modernize-macro-to-enum)
#define NLOHMANN_JSON_VERSION_MINOR 12 // NOLINT(modernize-macro-to-enum)
#define NLOHMANN_JSON_VERSION_PATCH 0 // NOLINT(modernize-macro-to-enum)
#ifndef JSON_DIAGNOSTICS
#define JSON_DIAGNOSTICS 0
#endif
#ifndef JSON_DIAGNOSTIC_POSITIONS
#define JSON_DIAGNOSTIC_POSITIONS 0
#endif
#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
#define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0
#endif
#if JSON_DIAGNOSTICS
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag
#else
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS
#endif
#if JSON_DIAGNOSTIC_POSITIONS
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS _dp
#else
#define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS
#endif
#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON
#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp
#else
#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
#endif
// Construct the namespace ABI tags component
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) json_abi ## a ## b ## c
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c) \
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c)
#define NLOHMANN_JSON_ABI_TAGS \
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \
NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON, \
NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS)
// Construct the namespace version component
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
_v ## major ## _ ## minor ## _ ## patch
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(major, minor, patch) \
NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch)
#if NLOHMANN_JSON_NAMESPACE_NO_VERSION
#define NLOHMANN_JSON_NAMESPACE_VERSION
#else
#define NLOHMANN_JSON_NAMESPACE_VERSION \
NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(NLOHMANN_JSON_VERSION_MAJOR, \
NLOHMANN_JSON_VERSION_MINOR, \
NLOHMANN_JSON_VERSION_PATCH)
#endif
// Combine namespace components
#define NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) a ## b
#define NLOHMANN_JSON_NAMESPACE_CONCAT(a, b) \
NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b)
#ifndef NLOHMANN_JSON_NAMESPACE
#define NLOHMANN_JSON_NAMESPACE \
nlohmann::NLOHMANN_JSON_NAMESPACE_CONCAT( \
NLOHMANN_JSON_ABI_TAGS, \
NLOHMANN_JSON_NAMESPACE_VERSION)
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_BEGIN
#define NLOHMANN_JSON_NAMESPACE_BEGIN \
namespace nlohmann \
{ \
inline namespace NLOHMANN_JSON_NAMESPACE_CONCAT( \
NLOHMANN_JSON_ABI_TAGS, \
NLOHMANN_JSON_NAMESPACE_VERSION) \
{
#endif
#ifndef NLOHMANN_JSON_NAMESPACE_END
#define NLOHMANN_JSON_NAMESPACE_END \
} /* namespace (inline namespace) NOLINT(readability/namespace) */ \
} // namespace nlohmann
#endif
/*!
@brief namespace for Niels Lohmann
@see https://github.com/nlohmann
@since version 1.0.0
*/
NLOHMANN_JSON_NAMESPACE_BEGIN
/*!
@brief default JSONSerializer template argument
This serializer ignores the template arguments and uses ADL
([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl))
for serialization.
*/
template<typename T = void, typename SFINAE = void>
struct adl_serializer;
/// a class to store JSON values
/// @sa https://json.nlohmann.me/api/basic_json/
template<template<typename U, typename V, typename... Args> class ObjectType =
std::map,
template<typename U, typename... Args> class ArrayType = std::vector,
class StringType = std::string, class BooleanType = bool,
class NumberIntegerType = std::int64_t,
class NumberUnsignedType = std::uint64_t,
class NumberFloatType = double,
template<typename U> class AllocatorType = std::allocator,
template<typename T, typename SFINAE = void> class JSONSerializer =
adl_serializer,
class BinaryType = std::vector<std::uint8_t>, // cppcheck-suppress syntaxError
class CustomBaseClass = void>
class basic_json;
/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document
/// @sa https://json.nlohmann.me/api/json_pointer/
template<typename RefStringType>
class json_pointer;
/*!
@brief default specialization
@sa https://json.nlohmann.me/api/json/
*/
using json = basic_json<>;
/// @brief a minimal map-like container that preserves insertion order
/// @sa https://json.nlohmann.me/api/ordered_map/
template<class Key, class T, class IgnoredLess, class Allocator>
struct ordered_map;
/// @brief specialization that maintains the insertion order of object keys
/// @sa https://json.nlohmann.me/api/ordered_json/
using ordered_json = basic_json<nlohmann::ordered_map>;
NLOHMANN_JSON_NAMESPACE_END
#endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_

View File

@ -1,132 +0,0 @@
#include "runner.h"
#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <sstream>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <nlohmann/json.hpp>
#include <openssl/bio.h>
#include <openssl/evp.h>
// Simple testing framework
#define TESTRESULT(condition, description) \
do { \
std::cout << (condition ? "\033[32m[PASS]\033[0m " : "\033[31m[FAIL]\033[0m ") << description; \
if (!(condition)) { \
std::cout << " - Expected condition to be true"; \
} \
std::cout << std::endl; \
} while(0)
// using json = nlohmann::json;
// std::string base64_decode(const std::string& encoded) {
// BIO* bio, *b64;
// int decodeLen = (encoded.length() * 3) / 4;
// std::string decoded(decodeLen, '\0');
// bio = BIO_new_mem_buf(encoded.data(), encoded.length());
// b64 = BIO_new(BIO_f_base64());
// bio = BIO_push(b64, bio);
// BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL);
// int len = BIO_read(bio, &decoded[0], encoded.length());
// decoded.resize(len > 0 ? len : 0);
// BIO_free_all(bio);
// return decoded;
// }
// int main(int argc, char* argv[]) {
// if (argc != 2) {
// std::cerr << "Usage: runner BASE64COMMAND\n";
// return -1;
// }
// std::string decoded = base64_decode(argv[1]);
// json j;
// try {
// j = json::parse(decoded);
// } catch (...) {
// std::cerr << "Invalid JSON in decoded command\n";
// return -1;
// }
// std::string command = j.value("command", "");
// std::vector<std::string> args = j.value("args", std::vector<std::string>{});
// std::string working_dir = j.value("working_dir", "");
// std::map<std::string, std::string> env = j.value("env", std::map<std::string, std::string>{});
// bool silent = j.value("silent", false);
// bool interactive = j.value("interactive", false);
// sSSHInfo* sshinfo = nullptr;
// sSSHInfo ssh;
// if (j.contains("sshinfo")) {
// ssh.host = j["sshinfo"].value("host", "");
// ssh.user = j["sshinfo"].value("user", "");
// ssh.port = j["sshinfo"].value("port", "");
// sshinfo = &ssh;
// }
// std::string output;
// int ret = execute_cmd(command, args, working_dir, env, silent, interactive, sshinfo, &output);
// if (!silent && !output.empty()) {
// std::cout << output;
// }
// return ret;
// }
void test_docker() {
std::string command = "docker";
std::vector<std::string> args = {"exec", "-it", "squashkiwi", "/bin/bash"};
std::string working_dir = ".";
std::map<std::string, std::string> env = {};
bool silent = false;
bool interactive = true;
runner::sSSHInfo ssh;
ssh.host = "10.10.10.13";
ssh.user = "katie";
ssh.port = "22";
runner::execute_cmd(command, args, working_dir, env, silent, interactive, &ssh);
}
void test_ssh() {
std::string command = "bash";
std::vector<std::string> args = {"-c", "ls -l && echo \"$WHATSUP\""};
std::string working_dir = "/home";
std::map<std::string, std::string> env = {{"WHATSUP", "Waaaaattttsssuuuppppp!"}};
bool silent = true;
bool interactive = false;
runner::sSSHInfo ssh;
ssh.host = "10.10.10.13";
ssh.user = "katie";
ssh.port = "22";
std::string output;
int ret = runner::execute_cmd(command, args, working_dir, env, silent, interactive, &ssh, &output);
TESTRESULT(ret == 0, "SSH test (ls and echo).");
}
void test_env() {
std::string command = "bash";
// Simplify the command to avoid nested quoting issues
std::vector<std::string> args = {"-c", "echo $WHATSUP"};
std::string working_dir = "/home";
std::map<std::string, std::string> env = {{"WHATSUP", "Waaaaattttsssuuuppppp!"}};
bool silent = false;
bool interactive = false;
runner::sSSHInfo ssh;
ssh.host = "10.10.10.13";
ssh.user = "katie";
ssh.port = "22";
// Capture output explicitly
std::string output;
int ret = runner::execute_cmd(command, args, working_dir, env, silent, interactive, &ssh, &output);
TESTRESULT(output == "Waaaaattttsssuuuppppp!","Whatsupppp test (remote env).");
}
int main(int argc, char* argv[]) {
test_ssh();
test_env();
}

View File

@ -5,7 +5,9 @@
#include "templates.hpp"
#include "utils/utils.hpp"
#include "utils/json.hpp"
#include "utils/execute.hpp"
#include "utils/runner.hpp"
#include <libassert/assert.hpp>
#include <iostream>
#include <memory>
@ -102,94 +104,82 @@ std::string server_env_manager::get_variable(const std::string& name) const {
return it->second;
}
sCommand server_env_manager::construct_standard_template_run_cmd(const std::string &service_name, const std::string &command, std::vector<std::string> args, bool silent) const
{
if (command.empty())
return sCommand();
// sCommand server_env_manager::construct_standard_template_run_cmd(const std::string &service_name, const std::string &command, std::vector<std::string> args, bool silent) const
// {
// if (command.empty())
// return sCommand();
std::string remote_service_template_path = remotepath::service_template(mServerName,service_name);
std::string script_path = remote_service_template_path + "/" + command + ".sh";
// std::string remote_service_template_path = remotepath::service_template(mServerName,service_name);
// std::string script_path = remote_service_template_path + "/" + command + ".sh";
std::map<std::string, std::string> env_vars;
if (!get_all_service_env_vars(mServerName, service_name, env_vars)) {
std::cerr << "Error: Failed to get all service env vars for " << service_name << std::endl;
return sCommand();
}
// std::map<std::string, std::string> env_vars;
// if (!get_all_service_env_vars(mServerName, service_name, env_vars)) {
// std::cerr << "Error: Failed to get all service env vars for " << service_name << std::endl;
// return sCommand();
// }
std::string argstr = "";
for (const auto& arg : args) {
argstr += " " + quote(dequote(trim(arg)));
}
// std::string argstr = "";
// for (const auto& arg : args) {
// argstr += " " + quote(dequote(trim(arg)));
// }
sCommand scommand(remote_service_template_path, "bash " + quote(script_path) + argstr + (silent ? " > /dev/null 2>&1" : ""), env_vars);
// sCommand scommand(remote_service_template_path, "bash " + quote(script_path) + argstr + (silent ? " > /dev/null 2>&1" : ""), env_vars);
if (scommand.empty())
std::cerr << "Error: Failed to construct command for " << service_name << " " << command << std::endl;
// if (scommand.empty())
// std::cerr << "Error: Failed to construct command for " << service_name << " " << command << std::endl;
return scommand;
}
// return scommand;
// }
bool server_env_manager::check_remote_dir_exists(const std::string &dir_path) const
{
sCommand scommand("test -d " + quote(dir_path));
return execute_ssh_command(get_SSH_INFO(), scommand, cMode::Silent);
return 0==runner::execute_cmd("test",{"-d", quote(dir_path)}, {}, {}, true, false, &get_SSH_INFO());
}
bool server_env_manager::check_remote_file_exists(const std::string& file_path) const {
sCommand scommand("test -f " + quote(file_path));
return execute_ssh_command(get_SSH_INFO(), scommand, cMode::Silent);
return 0==runner::execute_cmd("test",{"-f",quote(file_path)}, {}, {}, true, false, &get_SSH_INFO());
}
bool server_env_manager::check_remote_items_exist(const std::vector<std::string> &file_paths) const
{
// convert file_paths to a single string, separated by spaces
std::string file_paths_str;
std::string file_names_str;
for (const auto& file_path : file_paths) {
file_paths_str += quote(file_path) + " ";
file_names_str += std::filesystem::path(file_path).filename().string() + " ";
}
// check if all items in the vector exist on the remote server, in a single command.
sCommand scommand("for item in " + file_paths_str + "; do test -f $item; done");
bool okay = execute_ssh_command(get_SSH_INFO(), scommand, cMode::Silent);
if (!okay) {
std::cerr << "Error: Required items not found on remote server: " << file_names_str << std::endl;
return 0==runner::execute_cmd("bash",{"-c","for item in " + file_paths_str + "; do test -f $item; done"}, {}, {}, true, false, &get_SSH_INFO());
}
bool server_env_manager::run_remote_template_command(const std::string &service_name, const std::string &command, std::vector<std::string> args, bool silent, std::map<std::string, std::string> extra_env_vars, std::string * output) const
{
std::string working_dir = remotepath::service_template(mServerName,service_name);
std::string script_path = working_dir + "/" + command + ".sh";
std::string argstr = "";
for (const auto& arg : args) {
argstr += " " + quote(dequote(trim(arg)));
}
std::map<std::string, std::string> env_vars;
if (!get_all_service_env_vars(mServerName, service_name, env_vars)) {
std::cerr << "Error: Failed to get all service env vars for " << service_name << std::endl;
return false;
}
return true;
}
bool server_env_manager::run_remote_template_command(const std::string &service_name, const std::string &command, std::vector<std::string> args, bool silent, std::map<std::string, std::string> extra_env_vars) const
{
sCommand scommand = construct_standard_template_run_cmd(service_name, command, args, silent);
// add the extra env vars to the command
for (const auto& [key, value] : extra_env_vars)
scommand.add_env_var(key, value);
env_vars[key] = value;
if (scommand.get_command_to_run().empty())
return false;
cMode mode = (command=="ssh") ? (cMode::Interactive | cMode::RawCommand) : cMode::Silent;
return execute_ssh_command(get_SSH_INFO(), scommand, mode);
bool interactive = (command=="ssh");
ASSERT(!output || !silent); // if output is captured, silent must be false
ASSERT(!interactive || !silent); // if command is ssh, silent must be false
return 0==runner::execute_cmd("bash",{"-c", quote(script_path) + argstr}, working_dir, env_vars, silent, interactive, &get_SSH_INFO(), output);
}
bool server_env_manager::run_remote_template_command_and_capture_output(const std::string &service_name, const std::string &command, std::vector<std::string> args, std::string &output, bool silent, std::map<std::string, std::string> extra_env_vars) const
{
sCommand scommand = construct_standard_template_run_cmd(service_name, command, args, false);
if (scommand.get_command_to_run().empty())
return false;
// add the extra env vars to the command
for (const auto& [key, value] : extra_env_vars)
scommand.add_env_var(key, value);
cMode mode = cMode::CaptureOutput | cMode::RawCommand;
return execute_ssh_command(get_SSH_INFO(), scommand, mode, &output);
}
// base64 <<< "FOO=BAR WHEE=YAY bash ./test.sh"
// echo YmFzaCAtYyAnRk9PPUJBUiBXSEVFPVlBWSBiYXNoIC4vdGVzdC5zaCcK | base64 -d | bash

View File

@ -9,7 +9,6 @@
#include <map>
#include <memory>
#include <vector>
#include "utils/execute.hpp"
namespace dropshell {
@ -43,7 +42,7 @@ class server_env_manager {
std::string get_SSH_USER() const { return get_variable("SSH_USER"); }
std::string get_SSH_PORT() const { return get_variable("SSH_PORT"); }
std::string get_DROPSHELL_DIR() const { return get_variable("DROPSHELL_DIR"); }
sSSHInfo get_SSH_INFO() const { return sSSHInfo{get_SSH_HOST(), get_SSH_USER(), get_SSH_PORT()}; }
runner::sSSHInfo get_SSH_INFO() const { return runner::sSSHInfo{get_SSH_HOST(), get_SSH_USER(), get_SSH_PORT()}; }
bool is_valid() const { return mValid; }
std::string get_server_name() const { return mServerName; }
@ -54,12 +53,10 @@ class server_env_manager {
bool check_remote_items_exist(const std::vector<std::string>& file_paths) const;
bool run_remote_template_command(const std::string& service_name, const std::string& command,
std::vector<std::string> args, bool silent, std::map<std::string, std::string> extra_env_vars) const;
bool run_remote_template_command_and_capture_output(const std::string& service_name, const std::string& command,
std::vector<std::string> args, std::string & output, bool silent, std::map<std::string, std::string> extra_env_vars) const;
std::vector<std::string> args = {}, bool silent = false, std::map<std::string, std::string> extra_env_vars = {}, std::string * output = nullptr) const;
private:
sCommand construct_standard_template_run_cmd(const std::string& service_name, const std::string& command, std::vector<std::string> args, bool silent) const;
//sCommand construct_standard_template_run_cmd(const std::string& service_name, const std::string& command, std::vector<std::string> args, bool silent) const;
private:
std::string mServerName;

View File

@ -1,4 +1,3 @@
#include <iostream>
#include <fstream>
#include <sstream>
@ -16,7 +15,7 @@
#include "services.hpp"
#include "utils/directories.hpp"
#include "utils/utils.hpp"
#include "utils/runner.hpp"
namespace fs = std::filesystem;
@ -54,17 +53,14 @@ bool service_runner::install(bool silent) {
return false;
// Create service directory
std::string remote_service_path = remotepath::service(mServer, mService);
std::string mkdir_cmd = "mkdir -p " + quote(remote_service_path);
if (!execute_ssh_command(mServerEnv.get_SSH_INFO(), sCommand(mkdir_cmd), cMode::Silent))
if (0!=runner::execute_cmd("mkdir -p " + remotepath::service(mServer, mService),{},"",{},true,false,&mServerEnv.get_SSH_INFO()))
{
std::cerr << "Failed to create service directory " << remote_service_path << std::endl;
std::cerr << "Failed to create service directory " << remotepath::service(mServer, mService) << std::endl;
return false;
}
// Check if rsync is installed on remote host
std::string check_rsync_cmd = "which rsync > /dev/null 2>&1";
if (!execute_ssh_command(mServerEnv.get_SSH_INFO(), sCommand(check_rsync_cmd), cMode::Silent))
if (0!=runner::execute_cmd("which rsync",{},"",{},true,false,&mServerEnv.get_SSH_INFO()))
{
std::cerr << "rsync is not installed on the remote host" << std::endl;
return false;
@ -72,16 +68,7 @@ bool service_runner::install(bool silent) {
// Copy template files
{
std::cout << "Copying: [LOCAL] " << tinfo.local_template_path() << std::endl << std::string(8,' ')<<"[REMOTE] " << remotepath::service_template(mServer, mService) << "/" << std::endl;
std::string rsync_cmd = "rsync --delete -zrpc -e 'ssh -p " + mServerEnv.get_SSH_PORT() + "' " +
quote(tinfo.local_template_path().string()+"/") + " "+
mServerEnv.get_SSH_USER() + "@" + mServerEnv.get_SSH_HOST() + ":" +
quote(remotepath::service_template(mServer, mService)+"/");
//std::cout << std::endl << rsync_cmd << std::endl << std::endl;
if (!execute_local_command(rsync_cmd, silent ? cMode::Silent : cMode::None))
{
std::cerr << "Failed to copy template files using rsync" << std::endl;
std::cerr << "Is rsync installed on the remote host?" << std::endl;
if (!rsync_copy(tinfo.local_template_path().string()+"/", remotepath::service_template(mServer, mService)+"/", silent)) {
return false;
}
}
@ -93,14 +80,8 @@ bool service_runner::install(bool silent) {
std::cerr << "Error: Service directory not found: " << local_service_path << std::endl;
return false;
}
std::cout << "Copying: [LOCAL] " << local_service_path << std::endl <<std::string(8,' ')<<"[REMOTE] " << remotepath::service_config(mServer,mService) << std::endl;
std::string rsync_cmd = "rsync --delete -zrpc -e 'ssh -p " + mServerEnv.get_SSH_PORT() + "' " +
quote(local_service_path + "/") + " "+
mServerEnv.get_SSH_USER() + "@" + mServerEnv.get_SSH_HOST() + ":" +
quote(remotepath::service_config(mServer,mService) + "/");
if (!execute_local_command(rsync_cmd, silent ? cMode::Silent : cMode::None))
{
std::cerr << "Failed to copy service files using rsync" << std::endl;
if (!rsync_copy(local_service_path + "/", remotepath::service_config(mServer,mService) + "/", silent)) {
return false;
}
}
@ -141,12 +122,9 @@ bool service_runner::uninstall(bool silent) {
}
// 4. Remove the service directory from the server
std::string rm_cmd = "rm -rf " + quote(remotepath::service(mServer, mService));
if (!execute_ssh_command(mServerEnv.get_SSH_INFO(), sCommand(rm_cmd), cMode::Silent)) {
std::cerr << "Failed to remove service directory" << std::endl;
return false;
}
if (0!=runner::execute_cmd("rm -rf " + quote(remotepath::service(mServer, mService)), {}, "", {}, true, false, &mServerEnv.get_SSH_INFO()))
std::cerr << "Failed to remove remote service directory at " << remotepath::service(mServer, mService) << std::endl;
std::cout << "Service " << mService << " successfully uninstalled from " << mServer << std::endl;
return true;
}
@ -189,12 +167,10 @@ bool service_runner::fullnuke()
return false;
}
std::string rm_cmd = "rm -rf " + quote(local_service_path);
if (!execute_local_command(rm_cmd, cMode::Silent)) {
std::cerr << "Failed to remove service directory" << std::endl;
return false;
}
if (0!=runner::execute_cmd("rm -rf " + quote(local_service_path), {}, "", {}, true, false, &mServerEnv.get_SSH_INFO()))
std::cerr << "Failed to remove local service directory at " << local_service_path << std::endl;
std::cout << "Service " << mService << " successfully fully nuked from " << mServer << std::endl;
return true;
}
@ -296,15 +272,13 @@ std::map<std::string, ServiceStatus> service_runner::get_all_services_status(std
return status;
}
std::string remote_service_template_path = remotepath::service_template(server_name,service_name);
std::string script_path = remote_service_template_path + "/shared/" + command + ".sh";
sCommand scommand(remote_service_template_path, "bash " + quote(script_path), {});
std::string cmd_path = remotepath::service_template(server_name,service_name) + "/shared/";
std::string output;
cMode mode = cMode::CaptureOutput | cMode::RawCommand;
if (!execute_ssh_command(env.get_SSH_INFO(), scommand, mode, &output))
if (0!=runner::execute_cmd("bash",{cmd_path+command+".sh"}, cmd_path, {}, true, false, &env.get_SSH_INFO(), &output))
{
std::cerr << "Error: Failed to run command script at " << cmd_path << std::endl;
return status;
}
std::stringstream ss(output);
std::string line;
@ -772,4 +746,20 @@ std::string service_runner::get_latest_backup_file(const std::string& server, co
return latest_file;
}
bool service_runner::rsync_copy(const std::string& local_path, const std::string& remote_path, bool silent) {
std::cout << "Copying: [LOCAL] " << local_path << std::endl << std::string(8,' ')<<"[REMOTE] " << remote_path << std::endl;
std::string rsync_cmd = "rsync --delete -zrpc -e 'ssh -p " + mServerEnv.get_SSH_PORT() + "' " +
quote(local_path) + " " +
mServerEnv.get_SSH_USER() + "@" + mServerEnv.get_SSH_HOST() + ":" +
quote(remote_path);
if (0 != runner::execute_cmd(rsync_cmd, {}, "", {}, true, false, &mServerEnv.get_SSH_INFO())) {
std::cerr << "Failed to copy files using rsync" << std::endl;
std::cerr << "Is rsync installed on the remote host?" << std::endl;
return false;
}
return true;
}
} // namespace dropshell

View File

@ -87,6 +87,8 @@ class service_runner {
// edit the service configuration file
void edit_service_config();
// Helper methods
bool rsync_copy(const std::string& local_path, const std::string& remote_path, bool silent);
public:
// utility functions

View File

@ -1,182 +0,0 @@
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <vector>
#include <iostream>
#include <string>
#include <cstdlib>
#include <sstream>
#include <libassert/assert.hpp>
#include "execute.hpp"
#include "contrib/base64.hpp"
#include "utils/utils.hpp"
bool EXITSTATUSCHECK(int ret) {
return (ret != -1 && WIFEXITED(ret) && (WEXITSTATUS(ret) == 0)); // ret is -1 if the command failed to execute.
}
namespace dropshell {
bool execute_local_command_interactive(const sCommand &command, bool silent)
{
if (command.get_command_to_run().empty())
return false;
std::string full_command = command.construct_cmd(cStyle::Raw); // Get the command string
pid_t pid = fork();
if (pid == -1) {
// Fork failed
perror("fork failed");
return false;
} else if (pid == 0) {
// Child process
std::vector<const char *> commandvec = {"bash", "-c", full_command.c_str(),NULL};
if (!silent) {
std::cout << "Executing command: ";
for (auto & x : commandvec) std::cout << x << " ";
std::cout << std::endl;
}
execvp(commandvec[0], const_cast<char* const*>(commandvec.data()));
// If execvp returns, it means an error occurred
perror("execvp failed");
exit(EXIT_FAILURE); // Exit child process on error
} else {
// Parent process
int ret;
// Wait for the child process to complete
waitpid(pid, &ret, 0);
return EXITSTATUSCHECK(ret);
}
}
bool execute_local_command_and_capture_output(const sCommand& command, std::string * output, cMode mode)
{
ASSERT(output != nullptr, "Output string must be provided");
ASSERT(is_raw(mode), "Capture output mode requires raw command mode");
ASSERT(!hasFlag(mode, cMode::Silent), "Silent mode is not allowed with capture output mode");
if (command.get_command_to_run().empty())
return false;
cStyle style = getStyle(mode);
std::string full_cmd = command.construct_cmd(style) + " 2>&1";
FILE *pipe = popen(full_cmd.c_str(), "r");
if (!pipe) {
return false;
}
char buffer[128];
while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
(*output) += buffer;
}
int ret = pclose(pipe);
return EXITSTATUSCHECK(ret);
}
bool execute_local_command(const sCommand & command, cMode mode, std::string * output /* = nullptr */)
{
if (hasFlag(mode, cMode::Interactive)) {
ASSERT(! hasFlag(mode, cMode::CaptureOutput), "Interactive mode and capture output mode cannot be used together");
ASSERT(output == nullptr, "Interactive mode and an output string cannot be used together");
ASSERT(is_raw(mode), "Interactive mode requires raw command mode");
return execute_local_command_interactive(command, hasFlag(mode, cMode::Silent));
}
if (hasFlag(mode, cMode::CaptureOutput)) {
ASSERT(output != nullptr, "Capture output mode requires an output string to be provided");
ASSERT(is_raw(mode), "Capture output mode requires raw command mode");
ASSERT(!hasFlag(mode, cMode::Silent), "Silent mode is not allowed with capture output mode");
return execute_local_command_and_capture_output(command, output, mode);
}
if (command.get_command_to_run().empty())
return false;
cStyle style = getStyle(mode);
std::string full_cmd = command.construct_cmd(style) + " 2>&1" + (hasFlag(mode, cMode::Silent) ? " > /dev/null" : "");
int ret = system(full_cmd.c_str());
bool ok = EXITSTATUSCHECK(ret);
if (!ok) {
std::cerr << "Error: Failed to execute command: " << std::endl;
std::cerr << full_cmd << std::endl;
}
return ok;
}
bool execute_ssh_command(const sSSHInfo &ssh_info, const sCommand &command, cMode mode, std::string *output)
{
if (command.get_command_to_run().empty())
return false;
ASSERT(!(hasFlag(mode, cMode::Interactive) && !is_raw(mode)), "Interactive mode requires raw command mode");
ASSERT(!(hasFlag(mode, cMode::CaptureOutput) && output == nullptr), "Capture output mode must be used with an output string");
std::stringstream ssh_cmd;
ssh_cmd << "ssh -p " << ssh_info.port << " " << (hasFlag(mode, cMode::Interactive) ? "-tt " : "")
<< ssh_info.user << "@" << ssh_info.host;
std::string cmdstr;
if (!is_raw(mode))
cmdstr = quote("bash -c " + command.construct_cmd(cStyle::Safe));
else
{
std::string raw_cmd = command.construct_cmd(cStyle::Raw);
ASSERT(raw_cmd.find("'") == std::string::npos, "Raw command must not contain single quotes");
cmdstr = "bash -c "+ halfquote(raw_cmd);
}
sCommand ssh_command(ssh_cmd.str() + " " + cmdstr);
bool rval = execute_local_command(ssh_command, mode, output);
if (!rval) {
std::cerr <<std::endl<<std::endl;
std::cerr << "Error: Failed to execute ssh command: { [" << ssh_command.get_directory_to_run_in() << "], [";
std::cerr << ssh_command.get_command_to_run() << "], [";
for (const auto& env_var : ssh_command.get_env_vars()) {
std::cerr << env_var.first << "=" << env_var.second << ", ";
}
std::cerr << "] }" << std::endl;
std::cerr <<std::endl<<std::endl;
}
return rval;
}
std::string makesafecmd(const std::string &command)
{
if (command.empty())
return "";
std::string encoded = base64_encode(dequote(trim(command)));
std::string commandstr = "echo " + encoded + " | base64 -d | bash";
return commandstr;
}
std::string sCommand::construct_cmd(cStyle style) const
{
if (mCmd.empty())
return "";
std::string cdcmd;
if (!mDir.empty())
cdcmd = "cd " + quote(mDir) + " && ";
std::string cmdstr;
for (const auto& env_var : mVars) {
cmdstr += env_var.first + "=" + quote(dequote(trim(env_var.second))) + " ";
}
cmdstr += mCmd;
if (is_safe(style))
cmdstr = makesafecmd(cmdstr);
return cdcmd + cmdstr;
}
} // namespace dropshell

View File

@ -1,83 +0,0 @@
#ifndef EXECUTE_HPP
#define EXECUTE_HPP
#include <string>
#include <map>
namespace dropshell {
class sCommand;
// mode bitset
enum class cMode {
None = 0,
Interactive = 1,
Silent = 2,
CaptureOutput = 4,
RawCommand = 8
};
enum class cStyle {
Safe = 0,
Raw = 1
};
inline cMode operator&(cMode lhs, cMode rhs) {return static_cast<cMode>(static_cast<int>(lhs) & static_cast<int>(rhs));}
inline cMode operator+(cMode lhs, cMode rhs) {return static_cast<cMode>(static_cast<int>(lhs) | static_cast<int>(rhs));}
inline cMode operator-(cMode lhs, cMode rhs) {return static_cast<cMode>(static_cast<int>(lhs) & ~static_cast<int>(rhs));}
inline cMode operator|(cMode lhs, cMode rhs) {return static_cast<cMode>(static_cast<int>(lhs) | static_cast<int>(rhs));}
inline cMode operator|=(cMode & lhs, cMode rhs) {return lhs = lhs | rhs;}
inline bool hasFlag(cMode mode, cMode flag) {return (mode & flag) == flag;}
inline bool is_safe(cStyle style) { return style == cStyle::Safe; }
inline bool is_raw(cStyle style) { return style == cStyle::Raw; }
inline bool is_raw(cMode mode) { return hasFlag(mode, cMode::RawCommand); }
inline cStyle getStyle(cMode mode) { return is_raw(mode) ? cStyle::Raw : cStyle::Safe; }
typedef struct sSSHInfo {
std::string host;
std::string user;
std::string port;
} sSSHInfo;
bool execute_local_command(const sCommand & command, cMode mode = cMode::None, std::string * output = nullptr);
bool execute_ssh_command(const sSSHInfo & ssh_info, const sCommand & command, cMode mode = cMode::None, std::string * output = nullptr);
std::string makesafecmd(const std::string& command);
// ------------------------------------------------------------------------------------------------
// class to hold a command to run on the remote server.
class sCommand {
public:
sCommand(std::string directory_to_run_in, std::string command_to_run, const std::map<std::string, std::string> & env_vars) :
mDir(directory_to_run_in), mCmd(command_to_run), mVars(env_vars) {}
sCommand(std::string command_to_run) :
mDir(""), mCmd(command_to_run), mVars({}) {}
sCommand() : mDir(""), mCmd(""), mVars({}) {}
std::string get_directory_to_run_in() const { return mDir; }
std::string get_command_to_run() const { return mCmd; }
const std::map<std::string, std::string>& get_env_vars() const { return mVars; }
void add_env_var(const std::string& key, const std::string& value) { mVars[key] = value; }
std::string construct_cmd(cStyle style) const;
bool empty() const { return mCmd.empty(); }
private:
std::string mDir;
std::string mCmd;
std::map<std::string, std::string> mVars;
};
} // namespace dropshell
#endif

View File

@ -1,4 +1,4 @@
#include "runner.h"
#include "runner.hpp"
#include <cstdlib>
#include <sstream>
#include <iostream>
@ -41,14 +41,6 @@ void trim(std::string* s) {
}
}
// Add working_dir to the forward declaration
ssh_session ssh_connect_and_auth(const sSSHInfo* sshinfo, const std::map<std::string, std::string>& env, std::string* error);
std::string ssh_build_remote_command(const std::string& command, const std::vector<std::string>& args, const std::string& working_dir, const std::map<std::string, std::string>& env);
std::string escape_shell_arg(const std::string& arg);
int ssh_interactive_shell_session(ssh_session session, ssh_channel channel, const std::string& remote_cmd_str, const std::string& command, std::string* output);
int ssh_exec_command(ssh_session session, ssh_channel channel, const std::string& remote_cmd_str, bool silent, std::string* output, const std::map<std::string, std::string>& env, const std::string& working_dir);
int local_execute_cmd(const std::string& command, const std::vector<std::string>& args, const std::string& working_dir, const std::map<std::string, std::string>& env, bool silent, bool interactive, std::string* output);
ssh_session ssh_connect_and_auth(const sSSHInfo* sshinfo, const std::map<std::string, std::string>& env, std::string* error) {
ssh_session session = ssh_new();
if (!session) {

View File

@ -1,4 +1,7 @@
#pragma once
#ifndef RUNNER_HPP
#define RUNNER_HPP
#include <string>
#include <vector>
#include <map>
@ -22,4 +25,6 @@ int execute_cmd(
std::string* output = nullptr
);
} // namespace runner
} // namespace runner
#endif // RUNNER_HPP