342 lines
10 KiB
C++
342 lines
10 KiB
C++
#include <iostream>
|
||
#include <string>
|
||
#include <vector>
|
||
#include <cstdlib>
|
||
#include <sstream>
|
||
#include <array>
|
||
#include <fstream>
|
||
#include <filesystem>
|
||
#include <limits>
|
||
|
||
using namespace std;
|
||
|
||
class DockerManager {
|
||
public:
|
||
void pullImage();
|
||
void runContainerInteractive();
|
||
void listContainers() const;
|
||
void listImages() const;
|
||
void startInteractive();
|
||
void startDetached();
|
||
void deleteImage();
|
||
void stopContainer();
|
||
void removeContainer();
|
||
void execShell();
|
||
void execDetachedCommand();
|
||
void createDockerfile();
|
||
void spinUpMySQL();
|
||
void showContainerIP();
|
||
|
||
private:
|
||
static void runCommand(const string& cmd);
|
||
vector<pair<string, string>> getContainerList() const;
|
||
string selectContainer(const string& prompt);
|
||
};
|
||
|
||
// ---------------- Core Utility ----------------
|
||
|
||
void DockerManager::runCommand(const string& cmd) {
|
||
system(cmd.c_str());
|
||
}
|
||
|
||
vector<pair<string, string>> DockerManager::getContainerList() const {
|
||
vector<pair<string, string>> containers;
|
||
array<char, 256> buffer{};
|
||
string result;
|
||
|
||
FILE* pipe = popen("docker ps -a --format '{{.ID}} {{.Names}}'", "r");
|
||
if (!pipe) return containers;
|
||
|
||
while (fgets(buffer.data(), buffer.size(), pipe) != nullptr) {
|
||
result = buffer.data();
|
||
stringstream ss(result);
|
||
string id, name;
|
||
ss >> id >> name;
|
||
if (!id.empty() && !name.empty())
|
||
containers.emplace_back(id, name);
|
||
}
|
||
pclose(pipe);
|
||
return containers;
|
||
}
|
||
|
||
string DockerManager::selectContainer(const string& prompt) {
|
||
auto containers = getContainerList();
|
||
if (containers.empty()) {
|
||
cout << "No containers available.\n";
|
||
return "";
|
||
}
|
||
|
||
cout << "\nAvailable Containers:\n";
|
||
int i = 1;
|
||
for (const auto& c : containers)
|
||
cout << i++ << ". " << c.second << " (" << c.first.substr(0, 12) << ")\n";
|
||
|
||
int choice;
|
||
cout << prompt << " (1-" << containers.size() << "): ";
|
||
cin >> choice;
|
||
|
||
if (choice < 1 || choice > static_cast<int>(containers.size())) {
|
||
cout << "Invalid selection.\n";
|
||
return "";
|
||
}
|
||
|
||
return containers[choice - 1].first;
|
||
}
|
||
|
||
// ---------------- Docker Actions ----------------
|
||
|
||
void DockerManager::pullImage() {
|
||
string image;
|
||
cout << "Enter Docker image to pull (e.g., alpine): ";
|
||
cin >> image;
|
||
runCommand("docker pull " + image);
|
||
}
|
||
|
||
void DockerManager::runContainerInteractive() {
|
||
string image;
|
||
cout << "Enter Docker image to run interactively (e.g., alpine): ";
|
||
cin >> image;
|
||
|
||
int portCount;
|
||
cout << "How many port mappings? ";
|
||
cin >> portCount;
|
||
|
||
vector<string> ports;
|
||
for (int i = 0; i < portCount; ++i) {
|
||
string port;
|
||
cout << "Enter mapping #" << (i + 1)
|
||
<< " (format host:container, e.g., 8080:80): ";
|
||
cin >> port;
|
||
ports.push_back("-p " + port);
|
||
}
|
||
|
||
string portArgs;
|
||
for (const auto& p : ports) portArgs += p + " ";
|
||
|
||
cout << "\nPort Forwarding Explanation:\n"
|
||
<< " '-p hostPort:containerPort' exposes the container’s port to the host.\n"
|
||
<< " Example: '-p 8080:80' allows access via http://localhost:8080\n\n";
|
||
|
||
runCommand("docker run -it " + portArgs + image + " /bin/sh");
|
||
}
|
||
|
||
void DockerManager::listContainers() const { runCommand("docker ps -a"); }
|
||
void DockerManager::listImages() const { runCommand("docker images"); }
|
||
|
||
void DockerManager::startInteractive() {
|
||
string id = selectContainer("Select container to start interactively");
|
||
if (!id.empty()) runCommand("docker start -ai " + id);
|
||
}
|
||
|
||
void DockerManager::startDetached() {
|
||
string id = selectContainer("Select container to start detached");
|
||
if (!id.empty()) runCommand("docker start " + id);
|
||
}
|
||
|
||
void DockerManager::deleteImage() {
|
||
string image;
|
||
cout << "Enter image name or ID to delete: ";
|
||
cin >> image;
|
||
runCommand("docker rmi " + image);
|
||
}
|
||
|
||
void DockerManager::stopContainer() {
|
||
string id = selectContainer("Select container to stop");
|
||
if (!id.empty()) runCommand("docker stop " + id);
|
||
}
|
||
|
||
void DockerManager::removeContainer() {
|
||
string id = selectContainer("Select container to remove");
|
||
if (!id.empty()) runCommand("docker rm " + id);
|
||
}
|
||
|
||
void DockerManager::execShell() {
|
||
string id = selectContainer("Select running container for shell access");
|
||
if (!id.empty()) runCommand("docker exec -it " + id + " /bin/sh");
|
||
}
|
||
|
||
void DockerManager::execDetachedCommand() {
|
||
string id = selectContainer("Select container to run command in (detached)");
|
||
if (id.empty()) return;
|
||
|
||
// Flush any leftover newline before using getline
|
||
cin.ignore(numeric_limits<streamsize>::max(), '\n');
|
||
|
||
string command;
|
||
cout << "Enter command to execute inside the container: ";
|
||
getline(cin, command);
|
||
|
||
if (command.empty()) {
|
||
cout << "No command entered. Aborting.\n";
|
||
return;
|
||
}
|
||
|
||
string escapedCommand;
|
||
escapedCommand.reserve(command.size() * 2);
|
||
for (char c : command) {
|
||
if (c == '"' || c == '\\')
|
||
escapedCommand += '\\';
|
||
escapedCommand += c;
|
||
}
|
||
|
||
cout << "Executing command in detached mode...\n";
|
||
runCommand("docker exec -d " + id + " /bin/sh -c \"" + escapedCommand + "\"");
|
||
cout << "Command dispatched.\n";
|
||
}
|
||
|
||
void DockerManager::spinUpMySQL() {
|
||
string port, password, version;
|
||
cout << "Enter port mapping (e.g., 3306:3306): ";
|
||
cin >> port;
|
||
cout << "Enter MySQL root password: ";
|
||
cin >> password;
|
||
cout << "Enter MySQL version tag (e.g., 8): ";
|
||
cin >> version;
|
||
|
||
cout << "\nLaunching MySQL container (accessible via localhost:"
|
||
<< port.substr(0, port.find(':')) << ")\n";
|
||
runCommand("docker run -p " + port +
|
||
" --name mysql-container -e MYSQL_ROOT_PASSWORD=" + password +
|
||
" -d mysql:" + version);
|
||
}
|
||
|
||
// ---------------- Parsed IP Feature ----------------
|
||
|
||
void DockerManager::showContainerIP() {
|
||
string id = selectContainer("Select container to view IP");
|
||
if (id.empty()) return;
|
||
|
||
string command = "docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' " + id;
|
||
|
||
array<char, 128> buffer{};
|
||
string ip;
|
||
FILE* pipe = popen(command.c_str(), "r");
|
||
if (!pipe) {
|
||
cout << "Failed to inspect container.\n";
|
||
return;
|
||
}
|
||
|
||
if (fgets(buffer.data(), buffer.size(), pipe) != nullptr) {
|
||
ip = buffer.data();
|
||
}
|
||
pclose(pipe);
|
||
|
||
if (ip.empty() || ip == "\n")
|
||
cout << "No IP address found (container may be stopped or not attached to a network).\n";
|
||
else
|
||
cout << "Container IP Address: " << ip;
|
||
}
|
||
|
||
void DockerManager::createDockerfile() {
|
||
string baseImage, bashScriptPath, outputFile, imageName;
|
||
cout << "Enter base Docker image (e.g., ubuntu:22.04): ";
|
||
cin >> baseImage;
|
||
|
||
cout << "Enter path to bash script to convert (e.g., setup.sh): ";
|
||
cin >> bashScriptPath;
|
||
|
||
if (!filesystem::exists(bashScriptPath)) {
|
||
cout << "Error: Bash script not found.\n";
|
||
return;
|
||
}
|
||
|
||
cout << "Enter output Dockerfile name (e.g., Dockerfile or Dockerfile_app): ";
|
||
cin >> outputFile;
|
||
|
||
if (outputFile.empty()) outputFile = "Dockerfile";
|
||
|
||
ifstream scriptFile(bashScriptPath);
|
||
ofstream dockerfile(outputFile);
|
||
|
||
if (!scriptFile.is_open() || !dockerfile.is_open()) {
|
||
cout << "Error: Unable to open file(s).\n";
|
||
return;
|
||
}
|
||
|
||
// Write Dockerfile header
|
||
dockerfile << "FROM " << baseImage << "\n";
|
||
dockerfile << "WORKDIR /app\n\n";
|
||
dockerfile << "# Auto-generated by Tux-Dock\n";
|
||
|
||
string line;
|
||
while (getline(scriptFile, line)) {
|
||
if (line.empty()) continue;
|
||
|
||
// Skip comments or shebang
|
||
if (line.rfind("#", 0) == 0 || line.rfind("#!", 0) == 0)
|
||
continue;
|
||
|
||
dockerfile << "RUN " << line << "\n";
|
||
}
|
||
|
||
dockerfile << "\nCMD [\"/bin/bash\"]\n";
|
||
dockerfile.close();
|
||
scriptFile.close();
|
||
|
||
cout << "Dockerfile created successfully: " << outputFile << "\n";
|
||
cout << "Enter image name to build from this Dockerfile (e.g., myimage): ";
|
||
cin >> imageName;
|
||
|
||
if (imageName.empty()) {
|
||
cout << "No image name provided. Skipping build.\n";
|
||
return;
|
||
}
|
||
|
||
cout << "Building Docker image '" << imageName << "'...\n";
|
||
runCommand("docker build -t " + imageName + " -f " + outputFile + " .");
|
||
cout << "Docker build command executed.\n";
|
||
}
|
||
|
||
// ---------------- Menu ----------------
|
||
|
||
int main() {
|
||
DockerManager docker;
|
||
int option;
|
||
|
||
while (true) {
|
||
cout << "\nTux-Dock: Docker Management Menu\n"
|
||
<< "----------------------------------\n"
|
||
<< "1. Pull Docker Image\n"
|
||
<< "2. Run/Create Interactive Container\n"
|
||
<< "3. List All Containers\n"
|
||
<< "4. List All Images\n"
|
||
<< "5. Start Container Interactively (boot new session)\n"
|
||
<< "6. Start Detached Container Session\n"
|
||
<< "7. Delete Docker Image\n"
|
||
<< "8. Stop Container\n"
|
||
<< "9. Remove Container\n"
|
||
<< "10. Attach Shell to Running Container\n"
|
||
<< "11. Run Detached Command in Container\n"
|
||
<< "12. Spin Up MySQL Container\n"
|
||
<< "13. Get Container IP Address\n"
|
||
<< "14. Create Dockerfile & Build Image from Bash Script\n"
|
||
<< "15. Exit\n"
|
||
<< "----------------------------------\n"
|
||
<< "Choose an option: ";
|
||
|
||
cin >> option;
|
||
|
||
switch (option) {
|
||
case 1: docker.pullImage(); break;
|
||
case 2: docker.runContainerInteractive(); break;
|
||
case 3: docker.listContainers(); break;
|
||
case 4: docker.listImages(); break;
|
||
case 5: docker.startInteractive(); break;
|
||
case 6: docker.startDetached(); break;
|
||
case 7: docker.deleteImage(); break;
|
||
case 8: docker.stopContainer(); break;
|
||
case 9: docker.removeContainer(); break;
|
||
case 10: docker.execShell(); break;
|
||
case 11: docker.execDetachedCommand(); break;
|
||
case 12: docker.spinUpMySQL(); break;
|
||
case 13: docker.showContainerIP(); break;
|
||
case 14: docker.createDockerfile(); break;
|
||
case 15:
|
||
cout << "Exiting Tux-Dock.\n";
|
||
return 0;
|
||
default:
|
||
cout << "Invalid option.\n";
|
||
}
|
||
}
|
||
}
|