#include "docker_manager.hpp" #include "process_runner.hpp" #include #include #include #include using json = nlohmann::json; namespace { std::string joinPorts(const json& ports) { std::ostringstream result; bool first = true; for (const auto& port : ports) { const auto public_port = port.value("PublicPort", 0); const auto private_port = port.value("PrivatePort", 0); if (public_port == 0 || private_port == 0) continue; if (!first) result << ", "; result << public_port << ":" << private_port; first = false; } return result.str(); } std::string apiError(const EngineResponse& response, const std::string& fallback) { if (!response.error.empty()) return response.error; return fallback; } } // namespace std::string DockerManager::processError(const std::string& fallback, const std::string& stderr_text) { return stderr_text.empty() ? fallback : stderr_text; } bool DockerManager::runProcess(const std::vector& args, std::string& message, bool inherit_stdio) { ProcessOptions options; options.inherit_stdio = inherit_stdio; options.capture_stdout = !inherit_stdio; options.capture_stderr = !inherit_stdio; const ProcessResult result = ProcessRunner::run(args, options); if (!result.ok()) { message = processError("Docker command failed.", result.stderr_text); return false; } return true; } std::vector DockerManager::getContainerList() const { std::vector containers; const EngineResponse response = engine_.request("GET", "/containers/json?all=true"); if (!response.ok()) return containers; try { for (const auto& item : json::parse(response.body)) { ContainerInfo info; info.id = item.value("Id", ""); info.name = item.value("Names", std::vector{}).empty() ? "" : item.value("Names", std::vector{}).front(); if (!info.name.empty() && info.name.front() == '/') info.name.erase(0, 1); info.status = item.value("Status", ""); info.running = item.value("State", "") == "running"; info.ports = joinPorts(item.value("Ports", json::array())); if (!info.id.empty() && !info.name.empty()) containers.push_back(std::move(info)); } } catch (const json::exception&) { return {}; } return containers; } std::vector DockerManager::getImageList() const { std::vector images; const EngineResponse response = engine_.request("GET", "/images/json"); if (!response.ok()) return images; try { for (const auto& item : json::parse(response.body)) { const std::string id = item.value("Id", ""); const auto tags = item.value("RepoTags", std::vector{}); if (id.empty()) continue; if (tags.empty()) images.emplace_back(id, ""); else for (const auto& tag : tags) images.emplace_back(id, tag); } } catch (const json::exception&) { return {}; } return images; } bool DockerManager::pullImage(const std::string& image, std::string& message) const { if (image.empty()) { message = "Please provide an image name."; return false; } const bool ok = runProcess({"docker", "pull", image}, message); if (ok) message = "Image pulled successfully."; return ok; } bool DockerManager::runContainerInteractive(const std::string& image, const std::vector& ports, std::string& message) const { if (image.empty()) { message = "Please choose an image first."; return false; } std::vector args{"docker", "run", "-it"}; for (const auto& port : ports) { args.push_back("-p"); args.push_back(port); } args.insert(args.end(), {image, "/bin/sh"}); const bool ok = runProcess(args, message, true); if (ok) message = "Interactive container session finished."; return ok; } bool DockerManager::startInteractive(const std::string& id, std::string& message) const { const bool ok = runProcess({"docker", "start", "-ai", id}, message, true); if (ok) message = "Interactive container session finished."; return ok; } bool DockerManager::startDetached(const std::string& id, std::string& message) const { const EngineResponse response = engine_.request("POST", "/containers/" + id + "/start"); if (!response.ok()) { message = apiError(response, "Could not start that container."); return false; } message = "Container started in detached mode."; return true; } bool DockerManager::deleteImage(const std::string& id, std::string& message) const { const EngineResponse response = engine_.request("DELETE", "/images/" + id); if (!response.ok()) { message = apiError(response, "Could not delete that image."); return false; } message = "Image deleted."; return true; } bool DockerManager::stopContainer(const std::string& id, std::string& message) const { const EngineResponse response = engine_.request("POST", "/containers/" + id + "/stop"); if (!response.ok()) { message = apiError(response, "Could not stop that container."); return false; } message = "Container stopped."; return true; } bool DockerManager::removeContainer(const std::string& id, std::string& message) const { const EngineResponse response = engine_.request("DELETE", "/containers/" + id); if (!response.ok()) { message = apiError(response, "Could not remove that container."); return false; } message = "Container removed."; return true; } bool DockerManager::execShell(const std::string& id, std::string& message) const { const bool ok = runProcess({"docker", "exec", "-it", id, "/bin/sh"}, message, true); if (ok) message = "Shell session finished."; return ok; } bool DockerManager::execDetachedCommand(const std::string& id, const std::string& command, std::string& message) const { if (command.empty()) { message = "Please provide a command to run."; return false; } const bool ok = runProcess({"docker", "exec", "-d", id, "/bin/sh", "-c", command}, message); if (ok) message = "Command dispatched in detached mode."; return ok; } bool DockerManager::spinUpMySQL(const std::string& port, const std::string& password, const std::string& version, std::string& message) const { const bool ok = runProcess({"docker", "run", "-p", port, "--name", "mysql-container", "-e", "MYSQL_ROOT_PASSWORD=" + password, "-d", "mysql:" + version}, message); if (ok) message = "MySQL container launched."; return ok; } bool DockerManager::createDockerfile(const std::string& baseImage, const std::string& bashScriptPath, std::string outputFile, const std::string& imageName, std::string& message) const { if (baseImage.empty() || bashScriptPath.empty()) { message = "Base image and script path are required."; return false; } if (!std::filesystem::exists(bashScriptPath)) { message = "Could not find that script file."; return false; } if (outputFile.empty()) outputFile = "Dockerfile"; std::ifstream scriptFile(bashScriptPath); std::ofstream dockerfile(outputFile); if (!scriptFile.is_open() || !dockerfile.is_open()) { message = "Could not open one of the files."; return false; } dockerfile << "FROM " << baseImage << "\nWORKDIR /app\n\n# Auto-generated by Tux-Dock\n"; std::string line; while (std::getline(scriptFile, line)) { if (!line.empty() && line.rfind("#", 0) != 0) dockerfile << "RUN " << line << "\n"; } dockerfile << "\nCMD [\"/bin/bash\"]\n"; if (imageName.empty()) { message = "Dockerfile created. Build skipped because no image name was provided."; return true; } const bool ok = runProcess({"docker", "build", "-t", imageName, "-f", outputFile, "."}, message); if (ok) message = "Dockerfile created and image build completed."; return ok; }