v0.4.2 - Implemented the new Run Command in Container (with output) feature
This commit is contained in:
parent
23a461c584
commit
5ab40b082b
4 changed files with 403 additions and 25 deletions
93
image-setup-scripts/debian/forky/nginx-setup.sh
Normal file
93
image-setup-scripts/debian/forky/nginx-setup.sh
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Exit immediately if a command exits with a non-zero status
|
||||
set -e
|
||||
|
||||
# Ensure the script is run as root
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo "Error: Please run this script as root or with sudo." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== 1. Updating System Repositories ==="
|
||||
apt update && apt upgrade -y
|
||||
|
||||
echo "=== 2. Installing Nginx and PHP 8.4 ==="
|
||||
apt install -y nginx php8.4-fpm php8.4-cli php8.4-common
|
||||
|
||||
echo "=== 3. Adjusting PHP-FPM Timeouts ==="
|
||||
PHP_INI="/etc/php/8.4/fpm/php.ini"
|
||||
if [ -f "$PHP_INI" ]; then
|
||||
sed -i 's/^max_execution_time =.*/max_execution_time = 60/' "$PHP_INI"
|
||||
echo "Set max_execution_time = 60 in $PHP_INI"
|
||||
else
|
||||
echo "Warning: $PHP_INI not found, skipping inline adjustment."
|
||||
fi
|
||||
|
||||
echo "=== 4. Configuring Nginx Server Block ==="
|
||||
cat << 'EOF' > /etc/nginx/sites-available/default
|
||||
server {
|
||||
listen 80 default_server;
|
||||
listen [::]:80 default_server;
|
||||
|
||||
root /var/www/html;
|
||||
index index.php index.html index.htm;
|
||||
|
||||
server_name _;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
include snippets/fastcgi-php.conf;
|
||||
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
|
||||
|
||||
# Prevent Nginx from cutting off the connection prematurely
|
||||
fastcgi_read_timeout 60s;
|
||||
}
|
||||
|
||||
location ~ /\.ht {
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "=== 5. Creating the 30-Second Sleep Script ==="
|
||||
cat << 'EOF' > /var/www/html/sleep.php
|
||||
<?php
|
||||
ob_end_clean();
|
||||
header('Content-Type: text/plain');
|
||||
header('Cache-Control: no-cache');
|
||||
|
||||
echo "Test started at: " . date('H:i:s') . "\n";
|
||||
echo "Sleeping for 30 seconds...\n";
|
||||
flush();
|
||||
|
||||
sleep(30);
|
||||
|
||||
echo "Test finished at: " . date('H:i:s') . "\n";
|
||||
?>
|
||||
EOF
|
||||
|
||||
# Fixed typo: Added missing comment symbol here
|
||||
chown -R www-data:www-data /var/www/html/sleep.php
|
||||
|
||||
echo "=== 6. Validating Configuration ==="
|
||||
nginx -t
|
||||
|
||||
echo "=== 7. Starting Services via /etc/init.d/ ==="
|
||||
# Stop any existing processes safely before refreshing
|
||||
/etc/init.d/nginx stop || true
|
||||
/etc/init.d/php8.4-fpm stop || true
|
||||
|
||||
# Start PHP-FPM first to ensure the Unix domain socket is generated
|
||||
/etc/init.d/php8.4-fpm start
|
||||
/etc/init.d/nginx start
|
||||
|
||||
echo "====================================================="
|
||||
echo " Installation Complete!"
|
||||
echo " Test the setup via CLI using:"
|
||||
echo " time curl -i http://localhost/sleep.php"
|
||||
echo "====================================================="
|
||||
|
||||
298
main.cpp
298
main.cpp
|
|
@ -2,13 +2,15 @@
|
|||
#include "src/docker_state_poller.hpp"
|
||||
#include "src/operation_state.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <csignal>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <csignal>
|
||||
#include <ctime>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
|
|
@ -20,6 +22,7 @@
|
|||
#include <ftxui/component/component.hpp>
|
||||
#include <ftxui/component/screen_interactive.hpp>
|
||||
#include <ftxui/dom/elements.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace {
|
||||
|
||||
|
|
@ -101,14 +104,132 @@ void RemoveSignalTraps() {
|
|||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
|
||||
using nlohmann::json;
|
||||
|
||||
struct DetachedHistoryEntry {
|
||||
std::string command;
|
||||
std::string timestamp;
|
||||
std::string container;
|
||||
};
|
||||
|
||||
constexpr std::size_t kMaxDetachedHistoryEntries = 200;
|
||||
|
||||
std::filesystem::path GetDetachedHistoryPath() {
|
||||
const char* home = std::getenv("HOME");
|
||||
if (!home || !*home) return {};
|
||||
return std::filesystem::path(home) / ".tuxdock" / "detached-history.json";
|
||||
}
|
||||
|
||||
std::string CurrentTimestamp() {
|
||||
const auto now = std::chrono::system_clock::now();
|
||||
const auto time = std::chrono::system_clock::to_time_t(now);
|
||||
std::tm tm {};
|
||||
localtime_r(&time, &tm);
|
||||
std::ostringstream oss;
|
||||
oss << std::put_time(&tm, "%Y-%m-%d %H:%M:%S");
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
std::vector<DetachedHistoryEntry> LoadDetachedHistory() {
|
||||
const auto path = GetDetachedHistoryPath();
|
||||
if (path.empty() || !std::filesystem::exists(path)) return {};
|
||||
|
||||
std::ifstream file(path);
|
||||
if (!file) return {};
|
||||
|
||||
try {
|
||||
const auto data = json::parse(file);
|
||||
std::vector<DetachedHistoryEntry> result;
|
||||
for (const auto& item : data) {
|
||||
DetachedHistoryEntry entry;
|
||||
entry.command = item.value("command", "");
|
||||
entry.timestamp = item.value("timestamp", "");
|
||||
entry.container = item.value("container", "");
|
||||
if (!entry.command.empty()) result.push_back(std::move(entry));
|
||||
}
|
||||
return result;
|
||||
} catch (...) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
void SaveDetachedHistory(const std::vector<DetachedHistoryEntry>& entries) {
|
||||
const auto path = GetDetachedHistoryPath();
|
||||
if (path.empty()) return;
|
||||
|
||||
std::error_code ec;
|
||||
std::filesystem::create_directories(path.parent_path(), ec);
|
||||
|
||||
std::ofstream file(path);
|
||||
if (!file) return;
|
||||
|
||||
try {
|
||||
json data = json::array();
|
||||
for (const auto& entry : entries) {
|
||||
data.push_back({
|
||||
{"command", entry.command},
|
||||
{"timestamp", entry.timestamp},
|
||||
{"container", entry.container},
|
||||
});
|
||||
}
|
||||
file << data.dump(2);
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
||||
void AddDetachedHistoryEntry(const std::string& container,
|
||||
const std::string& command) {
|
||||
if (command.empty()) return;
|
||||
|
||||
auto entries = LoadDetachedHistory();
|
||||
entries.insert(entries.begin(), DetachedHistoryEntry{
|
||||
command,
|
||||
CurrentTimestamp(),
|
||||
container,
|
||||
});
|
||||
|
||||
std::size_t count = 0;
|
||||
const auto end = std::remove_if(entries.begin(), entries.end(),
|
||||
[&](const DetachedHistoryEntry& entry) {
|
||||
if (entry.container != container) return false;
|
||||
return ++count > kMaxDetachedHistoryEntries;
|
||||
});
|
||||
entries.erase(end, entries.end());
|
||||
SaveDetachedHistory(entries);
|
||||
}
|
||||
|
||||
void ClearDetachedHistory(const std::string& container) {
|
||||
auto entries = LoadDetachedHistory();
|
||||
const auto end = std::remove_if(entries.begin(), entries.end(),
|
||||
[&](const DetachedHistoryEntry& entry) {
|
||||
return entry.container == container;
|
||||
});
|
||||
entries.erase(end, entries.end());
|
||||
SaveDetachedHistory(entries);
|
||||
}
|
||||
|
||||
std::vector<DetachedHistoryEntry> LoadDetachedHistoryForContainer(
|
||||
const std::string& container) {
|
||||
const auto all = LoadDetachedHistory();
|
||||
std::vector<DetachedHistoryEntry> result;
|
||||
for (const auto& entry : all) {
|
||||
if (entry.container == container) result.push_back(entry);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class TuxDockApp {
|
||||
public:
|
||||
int Run();
|
||||
|
||||
private:
|
||||
enum class ModalMode { None, Input, Confirm, Select, Message, Busy };
|
||||
enum class ModalMode { None, Input, Confirm, Select, Message, Busy, History };
|
||||
struct RunContainerContext {
|
||||
std::string name;
|
||||
std::string image;
|
||||
|
|
@ -130,6 +251,7 @@ private:
|
|||
"Remove Container",
|
||||
"Attach Shell to Running Container",
|
||||
"Run Detached Command in Container",
|
||||
"Run Command in Container (with output)",
|
||||
"About Tux-Dock",
|
||||
"Exit",
|
||||
};
|
||||
|
|
@ -145,6 +267,12 @@ private:
|
|||
ftxui::Component menu_component_ = ftxui::Menu(&menu_entries_, &menu_selected_);
|
||||
ftxui::Component input_component_ = ftxui::Input(&modal_input_, "Type here");
|
||||
ftxui::Component select_component_ = ftxui::Menu(&modal_select_entries_, &modal_select_index_);
|
||||
std::vector<std::string> modal_history_display_;
|
||||
std::vector<DetachedHistoryEntry> modal_history_entries_;
|
||||
int modal_history_index_ = 0;
|
||||
ftxui::Component history_component_ = ftxui::Menu(&modal_history_display_, &modal_history_index_);
|
||||
std::string history_container_id_;
|
||||
std::string history_container_name_;
|
||||
std::function<void(bool, const std::string&)> input_callback_;
|
||||
std::function<void(bool)> confirm_callback_;
|
||||
std::function<void(bool, int)> select_callback_;
|
||||
|
|
@ -166,15 +294,18 @@ private:
|
|||
ftxui::Element FormatImageList(const std::vector<DockerManager::ImageInfo>& images) const;
|
||||
void SetStatus(const std::string& message) { status_ = message; }
|
||||
void OpenInput(const std::string&, const std::string&,
|
||||
std::function<void(bool, const std::string&)>, bool secret = false);
|
||||
std::function<void(bool, const std::string&)>, bool secret = false,
|
||||
const std::string& initial_value = "");
|
||||
void OpenConfirm(const std::string&, const std::string&, std::function<void(bool)>);
|
||||
void OpenSelect(const std::string&, const std::string&, std::vector<std::string>,
|
||||
std::function<void(bool, int)>);
|
||||
void OpenMessage(const std::string& title, const std::string& text);
|
||||
void OpenListMessage(const std::string& title, ftxui::Element content);
|
||||
void OpenHistory();
|
||||
void ResolveInput(bool);
|
||||
void ResolveConfirm(bool);
|
||||
void ResolveSelect(bool);
|
||||
void ResolveHistory();
|
||||
void CloseMessage();
|
||||
void ExecuteSelectedAction();
|
||||
void ActionPullImage();
|
||||
|
|
@ -187,6 +318,9 @@ private:
|
|||
void ActionRemoveContainer();
|
||||
void ActionExecShell();
|
||||
void ActionExecDetachedCommand();
|
||||
void OpenDetachedCommandInput(const std::string& initial_value = "");
|
||||
void ActionExecCommandWithOutput();
|
||||
void OpenCommandWithOutputInput(const std::string& initial_value = "");
|
||||
void ActionAbout();
|
||||
void PromptPortCountAndRun(const std::shared_ptr<RunContainerContext>&);
|
||||
void PromptNextPort(const std::shared_ptr<RunContainerContext>&, int);
|
||||
|
|
@ -197,7 +331,8 @@ private:
|
|||
const std::string&,
|
||||
std::function<void(const std::string&, const std::string&)>);
|
||||
void RunDeferredStatusAction(const std::string&, std::function<std::string()>);
|
||||
void BeginBusyOperation(const std::string&, const std::string&, std::function<std::string()>);
|
||||
void BeginBusyOperation(const std::string&, const std::string&, std::function<std::string()>,
|
||||
std::function<void(const std::string&)> on_complete = nullptr);
|
||||
void StartSpinner();
|
||||
void StopSpinner();
|
||||
void BeginStopOperation(const std::string& id, int timeout_seconds = 10);
|
||||
|
|
@ -307,11 +442,12 @@ void TuxDockApp::ApplyRefreshResults(
|
|||
void TuxDockApp::OpenInput(const std::string& title,
|
||||
const std::string& text,
|
||||
std::function<void(bool, const std::string&)> callback,
|
||||
bool secret) {
|
||||
bool secret,
|
||||
const std::string& initial_value) {
|
||||
modal_mode_ = ModalMode::Input;
|
||||
modal_title_ = title;
|
||||
modal_text_ = text;
|
||||
modal_input_.clear();
|
||||
modal_input_ = initial_value;
|
||||
|
||||
ftxui::InputOption option;
|
||||
option.password = secret;
|
||||
|
|
@ -463,7 +599,8 @@ void TuxDockApp::StopSpinner() {
|
|||
|
||||
void TuxDockApp::BeginBusyOperation(const std::string& title,
|
||||
const std::string& message,
|
||||
std::function<std::string()> action) {
|
||||
std::function<std::string()> action,
|
||||
std::function<void(const std::string&)> on_complete) {
|
||||
operation_state_.begin(title, message);
|
||||
modal_mode_ = ModalMode::Busy;
|
||||
spinner_frame_ = 0;
|
||||
|
|
@ -471,15 +608,19 @@ void TuxDockApp::BeginBusyOperation(const std::string& title,
|
|||
auto* active = screen_;
|
||||
StartSpinner();
|
||||
if (active) active->PostEvent(ftxui::Event::Custom);
|
||||
action_threads_.emplace_back([this, active, action = std::move(action)]() mutable {
|
||||
action_threads_.emplace_back([this, active, action = std::move(action), on_complete = std::move(on_complete)]() mutable {
|
||||
const auto message = action();
|
||||
if (active && ftxui::ScreenInteractive::Active() == active) {
|
||||
active->Post([this, message] {
|
||||
active->Post([this, message, on_complete = std::move(on_complete)] {
|
||||
RemoveSignalTraps();
|
||||
operation_state_.complete(message);
|
||||
StopSpinner();
|
||||
modal_mode_ = ModalMode::None;
|
||||
OpenMessage("Operation complete", message);
|
||||
if (on_complete) {
|
||||
on_complete(message);
|
||||
} else {
|
||||
OpenMessage("Operation complete", message);
|
||||
}
|
||||
state_poller_.trigger_now();
|
||||
});
|
||||
active->PostEvent(ftxui::Event::Custom);
|
||||
|
|
@ -707,23 +848,105 @@ void TuxDockApp::ActionExecDetachedCommand() {
|
|||
PromptContainerSelection(
|
||||
"Run Detached Command",
|
||||
[this](const std::string& id, const std::string& name) {
|
||||
OpenInput(
|
||||
"Detached Command", "Enter command to run in " + name + ":",
|
||||
[this, id](bool ok, const std::string& command) {
|
||||
if (!ok) return;
|
||||
BeginBusyOperation(
|
||||
"Running command", "Please wait...",
|
||||
[this, id, command] {
|
||||
std::string message;
|
||||
docker_.execDetachedCommand(id, command, message);
|
||||
return message;
|
||||
});
|
||||
});
|
||||
history_container_id_ = id;
|
||||
history_container_name_ = name;
|
||||
OpenDetachedCommandInput();
|
||||
});
|
||||
}
|
||||
|
||||
void TuxDockApp::OpenDetachedCommandInput(const std::string& initial_value) {
|
||||
OpenInput(
|
||||
"Detached Command",
|
||||
"Enter command to run in " + history_container_name_ + ": (F1: history)",
|
||||
[this](bool ok, const std::string& command) {
|
||||
if (!ok) return;
|
||||
AddDetachedHistoryEntry(history_container_name_, command);
|
||||
BeginBusyOperation(
|
||||
"Running command", "Please wait...",
|
||||
[this, command] {
|
||||
std::string message;
|
||||
docker_.execDetachedCommand(history_container_id_, command, message);
|
||||
return message;
|
||||
});
|
||||
},
|
||||
/*secret=*/false,
|
||||
initial_value);
|
||||
}
|
||||
|
||||
void TuxDockApp::OpenHistory() {
|
||||
modal_history_entries_ =
|
||||
LoadDetachedHistoryForContainer(history_container_name_);
|
||||
modal_history_display_.clear();
|
||||
modal_history_display_.push_back("[Enter new command]");
|
||||
for (const auto& entry : modal_history_entries_) {
|
||||
modal_history_display_.push_back("[" + entry.timestamp + "] " + entry.command);
|
||||
}
|
||||
modal_history_display_.push_back("[Clear history]");
|
||||
modal_history_index_ = 0;
|
||||
history_component_ = ftxui::Menu(&modal_history_display_, &modal_history_index_);
|
||||
modal_title_ = "Detached Command History - " + history_container_name_;
|
||||
modal_mode_ = ModalMode::History;
|
||||
}
|
||||
|
||||
void TuxDockApp::ResolveHistory() {
|
||||
const int selected = modal_history_index_;
|
||||
modal_mode_ = ModalMode::Input;
|
||||
|
||||
if (selected == 0) {
|
||||
modal_input_.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (selected == static_cast<int>(modal_history_display_.size()) - 1) {
|
||||
ClearDetachedHistory(history_container_name_);
|
||||
modal_input_.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& entry = modal_history_entries_[static_cast<std::size_t>(selected) - 1];
|
||||
modal_input_ = entry.command;
|
||||
}
|
||||
|
||||
void TuxDockApp::ActionExecCommandWithOutput() {
|
||||
PromptContainerSelection(
|
||||
"Run Command with Output",
|
||||
[this](const std::string& id, const std::string& name) {
|
||||
history_container_id_ = id;
|
||||
history_container_name_ = name;
|
||||
OpenCommandWithOutputInput();
|
||||
});
|
||||
}
|
||||
|
||||
void TuxDockApp::OpenCommandWithOutputInput(const std::string& initial_value) {
|
||||
OpenInput(
|
||||
"Command with Output",
|
||||
"Enter command to run in " + history_container_name_ + ": (F1: history)",
|
||||
[this](bool ok, const std::string& command) {
|
||||
if (!ok) return;
|
||||
AddDetachedHistoryEntry(history_container_name_, command);
|
||||
BeginBusyOperation(
|
||||
"Running command", "Please wait...",
|
||||
[this, command] {
|
||||
std::string output;
|
||||
std::string message;
|
||||
const bool success = docker_.execCommandWithOutput(
|
||||
history_container_id_, command, output, message);
|
||||
return success ? output : (message + "\n" + output);
|
||||
},
|
||||
[this](const std::string& result) {
|
||||
const bool failed = result.find("Command exited with code") == 0 ||
|
||||
result.find("Command terminated by signal") == 0;
|
||||
OpenListMessage(
|
||||
failed ? "Command failed" : "Command output",
|
||||
ftxui::vbox({ftxui::paragraph(result)}));
|
||||
});
|
||||
},
|
||||
/*secret=*/false,
|
||||
initial_value);
|
||||
}
|
||||
|
||||
void TuxDockApp::ActionAbout() {
|
||||
OpenMessage("About Tux-Dock", "Tux-Dock 0.4.1-beta | Created by markmental");
|
||||
OpenMessage("About Tux-Dock", "Tux-Dock 0.4.2-beta | Created by markmental");
|
||||
}
|
||||
|
||||
void TuxDockApp::ExecuteSelectedAction() {
|
||||
|
|
@ -738,8 +961,9 @@ void TuxDockApp::ExecuteSelectedAction() {
|
|||
case 7: ActionRemoveContainer(); break;
|
||||
case 8: ActionExecShell(); break;
|
||||
case 9: ActionExecDetachedCommand(); break;
|
||||
case 10: ActionAbout(); break;
|
||||
case 11:
|
||||
case 10: ActionExecCommandWithOutput(); break;
|
||||
case 11: ActionAbout(); break;
|
||||
case 12:
|
||||
if (screen_) screen_->ExitLoopClosure()();
|
||||
break;
|
||||
default: break;
|
||||
|
|
@ -752,6 +976,10 @@ bool TuxDockApp::OnEvent(ftxui::Event event) {
|
|||
return true;
|
||||
}
|
||||
if (modal_mode_ == ModalMode::Input) {
|
||||
if (event == ftxui::Event::F1) {
|
||||
OpenHistory();
|
||||
return true;
|
||||
}
|
||||
if (event == ftxui::Event::Return) {
|
||||
ResolveInput(true);
|
||||
return true;
|
||||
|
|
@ -762,6 +990,17 @@ bool TuxDockApp::OnEvent(ftxui::Event event) {
|
|||
}
|
||||
return input_component_->OnEvent(event);
|
||||
}
|
||||
if (modal_mode_ == ModalMode::History) {
|
||||
if (event == ftxui::Event::Return) {
|
||||
ResolveHistory();
|
||||
return true;
|
||||
}
|
||||
if (event == ftxui::Event::Escape) {
|
||||
modal_mode_ = ModalMode::Input;
|
||||
return true;
|
||||
}
|
||||
return history_component_->OnEvent(event);
|
||||
}
|
||||
if (modal_mode_ == ModalMode::Confirm) {
|
||||
if (event == ftxui::Event::Return || event == ftxui::Event::Character("y")) {
|
||||
ResolveConfirm(true);
|
||||
|
|
@ -817,6 +1056,13 @@ ftxui::Element TuxDockApp::RenderModal() const {
|
|||
select_component_->Render() | frame | vscroll_indicator,
|
||||
});
|
||||
footer = text("Up/Down: choose Enter: confirm Esc: cancel") | dim;
|
||||
} else if (modal_mode_ == ModalMode::History) {
|
||||
body = vbox(Elements{
|
||||
paragraph("Select a previous command for " + history_container_name_ + ":"),
|
||||
separator(),
|
||||
history_component_->Render() | frame | vscroll_indicator,
|
||||
});
|
||||
footer = text("Up/Down: choose Enter: select Esc: cancel") | dim;
|
||||
} else if (modal_mode_ == ModalMode::Busy) {
|
||||
static const std::string spinner = "|/-\\";
|
||||
Elements busy_elements = Elements{
|
||||
|
|
|
|||
|
|
@ -237,3 +237,38 @@ bool DockerManager::execDetachedCommand(const std::string& id,
|
|||
if (ok) message = "Command dispatched in detached mode.";
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool DockerManager::execCommandWithOutput(const std::string& id,
|
||||
const std::string& command,
|
||||
std::string& output,
|
||||
std::string& message) const {
|
||||
if (command.empty()) {
|
||||
message = "Please provide a command to run.";
|
||||
return false;
|
||||
}
|
||||
|
||||
ProcessOptions options;
|
||||
options.capture_stdout = true;
|
||||
options.capture_stderr = true;
|
||||
options.inherit_stdio = false;
|
||||
const ProcessResult result = ProcessRunner::run(
|
||||
{"docker", "exec", id, "/bin/sh", "-c", command}, options);
|
||||
|
||||
output = result.stdout_text;
|
||||
if (!result.stderr_text.empty()) {
|
||||
if (!output.empty()) output += "\n";
|
||||
output += result.stderr_text;
|
||||
}
|
||||
|
||||
if (result.signaled) {
|
||||
message = "Command terminated by signal " + std::to_string(result.signal) + ".";
|
||||
return false;
|
||||
}
|
||||
if (result.exit_code != 0) {
|
||||
message = "Command exited with code " + std::to_string(result.exit_code) + ".";
|
||||
return false;
|
||||
}
|
||||
|
||||
message = "Command completed successfully.";
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,10 @@ public:
|
|||
bool execDetachedCommand(const std::string& containerId,
|
||||
const std::string& command,
|
||||
std::string& message) const;
|
||||
bool execCommandWithOutput(const std::string& containerId,
|
||||
const std::string& command,
|
||||
std::string& output,
|
||||
std::string& message) const;
|
||||
private:
|
||||
std::unique_ptr<DockerEngineClient> engine_;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue