Update source/src/commands/help.cpp
This commit is contained in:
@@ -156,6 +156,7 @@ int help_handler(const CommandContext& ctx) {
|
||||
show_command("create-template");
|
||||
info << std::endl;
|
||||
show_command("validate");
|
||||
show_command("publish-template");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
346
source/src/commands/publish-template.cpp
Normal file
346
source/src/commands/publish-template.cpp
Normal file
@@ -0,0 +1,346 @@
|
||||
#include "command_registry.hpp"
|
||||
#include "config.hpp"
|
||||
#include "templates.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
#include "utils/directories.hpp"
|
||||
#include "utils/hash.hpp"
|
||||
#include "shared_commands.hpp"
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <ctime>
|
||||
#include <iomanip>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace dropshell {
|
||||
|
||||
void publish_template_autocomplete(const CommandContext& ctx);
|
||||
int publish_template_handler(const CommandContext& ctx);
|
||||
|
||||
static std::vector<std::string> publish_template_name_list = {"publish-template", "publish"};
|
||||
|
||||
// Static registration
|
||||
struct PublishTemplateCommandRegister {
|
||||
PublishTemplateCommandRegister() {
|
||||
CommandRegistry::instance().register_command({
|
||||
publish_template_name_list,
|
||||
publish_template_handler,
|
||||
publish_template_autocomplete,
|
||||
false, // hidden
|
||||
true, // requires_config
|
||||
true, // requires_install
|
||||
1, // min_args (directory required)
|
||||
2, // max_args (optional registry name + directory)
|
||||
"publish-template [REGISTRY] DIRECTORY",
|
||||
"Publish a template to a template registry.",
|
||||
R"HELP(
|
||||
Publishes a template directory to a template registry using sos.
|
||||
|
||||
Usage:
|
||||
ds publish-template DIRECTORY
|
||||
ds publish-template REGISTRY_NAME DIRECTORY
|
||||
|
||||
Arguments:
|
||||
REGISTRY_NAME Optional. Name of the template registry to publish to.
|
||||
If not specified, uses the first registry with a token.
|
||||
DIRECTORY Path to the template directory to publish.
|
||||
|
||||
The template is validated before publishing. Two tags are created:
|
||||
- YYYYMMDD (e.g., 20251228)
|
||||
- latest
|
||||
|
||||
Requirements:
|
||||
- Template must pass validation
|
||||
- Registry must have a token configured in dropshell.json
|
||||
- curl must be available (for sos download)
|
||||
|
||||
Example:
|
||||
ds publish-template ./my-template
|
||||
ds publish-template main ./my-template
|
||||
)HELP"
|
||||
});
|
||||
}
|
||||
} publish_template_command_register;
|
||||
|
||||
|
||||
void publish_template_autocomplete(const CommandContext& ctx) {
|
||||
if (ctx.args.size() == 0) {
|
||||
// List registry names
|
||||
std::vector<tRegistryEntry> registries = gConfig().get_template_registry_urls();
|
||||
for (const auto& reg : registries) {
|
||||
if (!reg.token.empty()) {
|
||||
rawout << reg.name << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get current date as YYYYMMDD
|
||||
static std::string get_date_tag() {
|
||||
auto now = std::time(nullptr);
|
||||
auto tm = *std::gmtime(&now);
|
||||
std::ostringstream oss;
|
||||
oss << std::put_time(&tm, "%Y%m%d");
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
// Get architecture string
|
||||
static std::string get_arch() {
|
||||
FILE* pipe = popen("uname -m", "r");
|
||||
if (!pipe) return "x86_64";
|
||||
|
||||
char buffer[128];
|
||||
std::string result;
|
||||
if (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
|
||||
result = buffer;
|
||||
// Remove trailing newline
|
||||
if (!result.empty() && result.back() == '\n') {
|
||||
result.pop_back();
|
||||
}
|
||||
}
|
||||
pclose(pipe);
|
||||
|
||||
if (result == "aarch64" || result == "arm64") {
|
||||
return "aarch64";
|
||||
}
|
||||
return "x86_64";
|
||||
}
|
||||
|
||||
// Download a tool to temp directory
|
||||
static bool download_tool(const std::string& tool_name, const std::string& temp_dir, std::string& tool_path) {
|
||||
std::string arch = get_arch();
|
||||
tool_path = temp_dir + "/" + tool_name;
|
||||
|
||||
std::string url = "https://getbin.xyz/" + tool_name + ":latest-" + arch;
|
||||
std::string cmd = "curl -fsSL -o \"" + tool_path + "\" \"" + url + "\" 2>/dev/null";
|
||||
|
||||
int ret = system(cmd.c_str());
|
||||
if (ret != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make executable
|
||||
std::string chmod_cmd = "chmod +x \"" + tool_path + "\"";
|
||||
system(chmod_cmd.c_str());
|
||||
|
||||
return std::filesystem::exists(tool_path);
|
||||
}
|
||||
|
||||
// Create tarball of template
|
||||
static bool create_tarball(const std::string& template_dir, const std::string& tarball_path) {
|
||||
std::filesystem::path dir_path(template_dir);
|
||||
std::string template_name = dir_path.filename().string();
|
||||
std::string parent_dir = dir_path.parent_path().string();
|
||||
|
||||
std::string cmd = "tar -czf \"" + tarball_path + "\" -C \"" + parent_dir + "\" \"" + template_name + "\" 2>/dev/null";
|
||||
int ret = system(cmd.c_str());
|
||||
|
||||
return ret == 0 && std::filesystem::exists(tarball_path);
|
||||
}
|
||||
|
||||
// Upload using sos
|
||||
static bool upload_with_sos(const std::string& sos_path, const std::string& server,
|
||||
const std::string& tarball, const std::string& label_tag,
|
||||
const std::string& hash, const std::string& token) {
|
||||
// Set environment variable for token
|
||||
setenv("SOS_WRITE_TOKEN", token.c_str(), 1);
|
||||
|
||||
std::string cmd = "\"" + sos_path + "\" upload " + server + " \"" + tarball + "\" \"" + label_tag + "\" --metadata \"unpackedhash=" + hash + "\" 2>&1";
|
||||
|
||||
FILE* pipe = popen(cmd.c_str(), "r");
|
||||
if (!pipe) return false;
|
||||
|
||||
char buffer[512];
|
||||
std::string output;
|
||||
while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
|
||||
output += buffer;
|
||||
}
|
||||
|
||||
int ret = pclose(pipe);
|
||||
|
||||
if (ret != 0) {
|
||||
error << output << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int publish_template_handler(const CommandContext& ctx) {
|
||||
std::string registry_name;
|
||||
std::string template_dir;
|
||||
|
||||
// Parse arguments
|
||||
if (ctx.args.size() == 1) {
|
||||
// Only directory provided
|
||||
template_dir = ctx.args[0];
|
||||
} else if (ctx.args.size() == 2) {
|
||||
// Registry name and directory provided
|
||||
registry_name = ctx.args[0];
|
||||
template_dir = ctx.args[1];
|
||||
} else {
|
||||
error << "Usage: ds publish-template [REGISTRY] DIRECTORY" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Resolve template directory to absolute path
|
||||
std::filesystem::path dir_path(template_dir);
|
||||
if (dir_path.is_relative()) {
|
||||
dir_path = std::filesystem::current_path() / dir_path;
|
||||
}
|
||||
dir_path = std::filesystem::canonical(dir_path);
|
||||
template_dir = dir_path.string();
|
||||
|
||||
// Check directory exists
|
||||
if (!std::filesystem::exists(dir_path) || !std::filesystem::is_directory(dir_path)) {
|
||||
error << "Directory not found: " << template_dir << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string template_name = dir_path.filename().string();
|
||||
|
||||
// Find registry
|
||||
std::vector<tRegistryEntry> registries = gConfig().get_template_registry_urls();
|
||||
tRegistryEntry* selected_registry = nullptr;
|
||||
|
||||
if (registry_name.empty()) {
|
||||
// Find first registry with a token
|
||||
for (auto& reg : registries) {
|
||||
if (!reg.token.empty()) {
|
||||
selected_registry = ®
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!selected_registry) {
|
||||
error << "No template registry with a token found in config" << std::endl;
|
||||
info << "Add a token to a registry in dropshell.json or specify a registry name" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
// Find registry by name
|
||||
for (auto& reg : registries) {
|
||||
if (reg.name == registry_name) {
|
||||
selected_registry = ®
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!selected_registry) {
|
||||
error << "Registry not found: " << registry_name << std::endl;
|
||||
return 1;
|
||||
}
|
||||
if (selected_registry->token.empty()) {
|
||||
error << "Registry '" << registry_name << "' does not have a token configured" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Extract server from URL (remove https:// prefix)
|
||||
std::string server = selected_registry->url;
|
||||
if (server.substr(0, 8) == "https://") {
|
||||
server = server.substr(8);
|
||||
} else if (server.substr(0, 7) == "http://") {
|
||||
server = server.substr(7);
|
||||
}
|
||||
// Remove trailing slash if present
|
||||
if (!server.empty() && server.back() == '/') {
|
||||
server.pop_back();
|
||||
}
|
||||
|
||||
maketitle("Publishing template: " + template_name);
|
||||
info << "Directory: " << template_dir << std::endl;
|
||||
info << "Registry: " << selected_registry->name << " (" << server << ")" << std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
// Step 1: Validate template
|
||||
info << "=== Validating Template ===" << std::endl;
|
||||
if (!template_manager::test_template(template_dir)) {
|
||||
error << "Template validation failed. Please fix issues before publishing." << std::endl;
|
||||
info << "Run: ds validate " << template_dir << std::endl;
|
||||
return 1;
|
||||
}
|
||||
info << " ✓ Template is valid" << std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
// Step 2: Create temp directory
|
||||
std::string temp_dir = std::filesystem::temp_directory_path().string() + "/dropshell-publish-" + std::to_string(getpid());
|
||||
std::filesystem::create_directories(temp_dir);
|
||||
|
||||
// Cleanup on exit
|
||||
struct TempDirCleaner {
|
||||
std::string path;
|
||||
~TempDirCleaner() {
|
||||
std::filesystem::remove_all(path);
|
||||
}
|
||||
} cleaner{temp_dir};
|
||||
|
||||
// Step 3: Calculate hash (using built-in hash function)
|
||||
info << "=== Calculating Hash ===" << std::endl;
|
||||
std::string hash = hash_directory_recursive(template_dir);
|
||||
if (hash.empty()) {
|
||||
error << "Failed to calculate directory hash" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
info << " Hash: " << hash << std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
// Step 4: Download sos tool
|
||||
info << "=== Downloading Tools ===" << std::endl;
|
||||
|
||||
std::string sos_path;
|
||||
|
||||
info << " Downloading sos..." << std::endl;
|
||||
if (!download_tool("sos", temp_dir, sos_path)) {
|
||||
error << "Failed to download sos" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
info << " ✓ sos downloaded" << std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
// Step 5: Create tarball
|
||||
info << "=== Creating Package ===" << std::endl;
|
||||
std::string tarball_path = temp_dir + "/" + template_name + ".tgz";
|
||||
if (!create_tarball(template_dir, tarball_path)) {
|
||||
error << "Failed to create tarball" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto file_size = std::filesystem::file_size(tarball_path);
|
||||
info << " Created " << template_name << ".tgz (" << (file_size / 1024) << " KB)" << std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
// Step 6: Upload with tags
|
||||
info << "=== Uploading to Registry ===" << std::endl;
|
||||
|
||||
std::string date_tag = get_date_tag();
|
||||
|
||||
// Upload with date tag
|
||||
std::string label_date = template_name + ":" + date_tag;
|
||||
info << " Uploading " << label_date << "..." << std::endl;
|
||||
if (!upload_with_sos(sos_path, server, tarball_path, label_date, hash, selected_registry->token)) {
|
||||
error << "Failed to upload " << label_date << std::endl;
|
||||
return 1;
|
||||
}
|
||||
info << " ✓ Uploaded " << label_date << std::endl;
|
||||
|
||||
// Upload with latest tag
|
||||
std::string label_latest = template_name + ":latest";
|
||||
info << " Uploading " << label_latest << "..." << std::endl;
|
||||
if (!upload_with_sos(sos_path, server, tarball_path, label_latest, hash, selected_registry->token)) {
|
||||
error << "Failed to upload " << label_latest << std::endl;
|
||||
return 1;
|
||||
}
|
||||
info << " ✓ Uploaded " << label_latest << std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
// Summary
|
||||
maketitle("Publish Complete");
|
||||
info << "Template '" << template_name << "' published successfully!" << std::endl;
|
||||
info << "Tags: " << date_tag << ", latest" << std::endl;
|
||||
info << "URL: https://" << server << "/" << template_name << ":latest" << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace dropshell
|
||||
Reference in New Issue
Block a user