feat: Update 2 files
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
#include <ctime>
|
||||
#include <iomanip>
|
||||
#include <cstdlib>
|
||||
#include <algorithm>
|
||||
|
||||
namespace dropshell {
|
||||
|
||||
@@ -31,9 +32,9 @@ struct PublishTemplateCommandRegister {
|
||||
false, // hidden
|
||||
true, // requires_config
|
||||
true, // requires_install
|
||||
1, // min_args (directory required)
|
||||
2, // max_args (optional registry name + directory)
|
||||
"publish-template [REGISTRY] DIRECTORY",
|
||||
1, // min_args
|
||||
3, // max_args (--all + optional registry + directory)
|
||||
"publish-template [--all] [REGISTRY] DIRECTORY",
|
||||
"Publish a template to a template registry.",
|
||||
R"HELP(
|
||||
Publishes a template directory to a template registry.
|
||||
@@ -41,11 +42,15 @@ Publishes a template directory to a template registry.
|
||||
Usage:
|
||||
ds publish-template DIRECTORY
|
||||
ds publish-template REGISTRY_NAME DIRECTORY
|
||||
ds publish-template --all DIRECTORY
|
||||
ds publish-template --all REGISTRY_NAME DIRECTORY
|
||||
|
||||
Arguments:
|
||||
--all Publish all templates in subdirectories of DIRECTORY.
|
||||
Only publishes subdirectories containing config/.template_info.env
|
||||
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.
|
||||
DIRECTORY Path to the template directory (or parent directory with --all).
|
||||
|
||||
The template is validated before publishing. Two tags are created:
|
||||
- YYYYMMDD (e.g., 20251228)
|
||||
@@ -64,6 +69,7 @@ Requirements:
|
||||
Example:
|
||||
ds publish-template ./my-template
|
||||
ds publish-template main ./my-template
|
||||
ds publish-template --all ./templates-dir
|
||||
SOS_WRITE_TOKEN=xxx ds publish-template ./my-template
|
||||
)HELP"
|
||||
});
|
||||
@@ -73,6 +79,7 @@ Example:
|
||||
|
||||
void publish_template_autocomplete(const CommandContext& ctx) {
|
||||
if (ctx.args.size() == 0) {
|
||||
rawout << "--all" << std::endl;
|
||||
// List registry names
|
||||
std::vector<tRegistryEntry> registries = gConfig().get_template_registry_urls();
|
||||
for (const auto& reg : registries) {
|
||||
@@ -262,43 +269,140 @@ static bool publish_to_registry(const std::string& server_url, const std::string
|
||||
}
|
||||
}
|
||||
|
||||
int publish_template_handler(const CommandContext& ctx) {
|
||||
std::string registry_name;
|
||||
std::string template_dir;
|
||||
// Check if a directory is a valid template (has config/.template_info.env)
|
||||
static bool is_valid_template_dir(const std::filesystem::path& dir_path) {
|
||||
return std::filesystem::exists(dir_path / "config" / ".template_info.env");
|
||||
}
|
||||
|
||||
// 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;
|
||||
// Publish a single template directory
|
||||
// Returns: 0 = success, 1 = error, 2 = skipped (not a template)
|
||||
static int publish_single_template(const std::string& template_dir, const std::string& server_url,
|
||||
const std::string& token, bool quiet = false) {
|
||||
std::filesystem::path dir_path(template_dir);
|
||||
std::string template_name = dir_path.filename().string();
|
||||
|
||||
if (!quiet) {
|
||||
maketitle("Publishing template: " + template_name);
|
||||
info << "Directory: " << template_dir << std::endl;
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
// Validate template
|
||||
if (!quiet) info << "=== Validating Template ===" << std::endl;
|
||||
if (!template_manager::test_template(template_dir)) {
|
||||
error << "Template '" << template_name << "' validation failed." << std::endl;
|
||||
if (!quiet) info << "Run: ds validate-template " << template_dir << std::endl;
|
||||
return 1;
|
||||
}
|
||||
if (!quiet) info << " ✓ Template is valid" << std::endl;
|
||||
if (!quiet) std::cout << std::endl;
|
||||
|
||||
// Calculate directory hash
|
||||
if (!quiet) info << "=== Calculating Hash ===" << std::endl;
|
||||
std::string unpacked_hash = hash_directory_recursive(template_dir);
|
||||
if (unpacked_hash.empty()) {
|
||||
error << "Failed to calculate directory hash for " << template_name << std::endl;
|
||||
return 1;
|
||||
}
|
||||
if (!quiet) info << " Hash: " << unpacked_hash << std::endl;
|
||||
if (!quiet) std::cout << std::endl;
|
||||
|
||||
// Create temp directory and tarball
|
||||
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};
|
||||
|
||||
if (!quiet) 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 for " << template_name << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Resolve template directory to absolute path
|
||||
std::filesystem::path dir_path(template_dir);
|
||||
auto file_size = std::filesystem::file_size(tarball_path);
|
||||
if (!quiet) info << " Created " << template_name << ".tgz (" << (file_size / 1024) << " KB)" << std::endl;
|
||||
if (!quiet) std::cout << std::endl;
|
||||
|
||||
// Publish with tags
|
||||
if (!quiet) info << "=== Publishing to Registry ===" << std::endl;
|
||||
|
||||
std::string date_tag = get_date_tag();
|
||||
std::vector<std::string> labeltags = {
|
||||
template_name + ":" + date_tag,
|
||||
template_name + ":latest"
|
||||
};
|
||||
|
||||
if (!quiet) info << " Tags: " << date_tag << ", latest" << std::endl;
|
||||
if (!quiet) info << " Uploading..." << std::endl;
|
||||
|
||||
if (!publish_to_registry(server_url, token, tarball_path, labeltags, unpacked_hash)) {
|
||||
error << "Failed to publish template " << template_name << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!quiet) {
|
||||
info << " ✓ Published successfully" << std::endl;
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int publish_template_handler(const CommandContext& ctx) {
|
||||
bool publish_all = false;
|
||||
std::string registry_name;
|
||||
std::string target_dir;
|
||||
|
||||
// Parse arguments - check for --all flag
|
||||
std::vector<std::string> args;
|
||||
for (const auto& arg : ctx.args) {
|
||||
if (arg == "--all" || arg == "-a" || arg == "all") {
|
||||
publish_all = true;
|
||||
} else {
|
||||
args.push_back(arg);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse remaining arguments
|
||||
if (args.size() == 1) {
|
||||
target_dir = args[0];
|
||||
} else if (args.size() == 2) {
|
||||
registry_name = args[0];
|
||||
target_dir = args[1];
|
||||
} else if (args.empty()) {
|
||||
error << "Usage: ds publish-template [--all] [REGISTRY] DIRECTORY" << std::endl;
|
||||
return 1;
|
||||
} else {
|
||||
error << "Too many arguments" << std::endl;
|
||||
error << "Usage: ds publish-template [--all] [REGISTRY] DIRECTORY" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Resolve directory to absolute path
|
||||
std::filesystem::path dir_path(target_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();
|
||||
target_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;
|
||||
error << "Directory not found: " << target_dir << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::string template_name = dir_path.filename().string();
|
||||
|
||||
// Find registry and token
|
||||
std::vector<tRegistryEntry> registries = gConfig().get_template_registry_urls();
|
||||
tRegistryEntry* selected_registry = nullptr;
|
||||
std::string effective_token; // Token to use (may come from config or env var)
|
||||
std::string effective_token;
|
||||
|
||||
if (registry_name.empty()) {
|
||||
// Find first registry with a token
|
||||
@@ -367,81 +471,79 @@ int publish_template_handler(const CommandContext& ctx) {
|
||||
server_url.pop_back();
|
||||
}
|
||||
|
||||
maketitle("Publishing template: " + template_name);
|
||||
info << "Directory: " << template_dir << std::endl;
|
||||
info << "Registry: " << selected_registry->name << " (" << server_url << ")" << std::endl;
|
||||
std::cout << std::endl;
|
||||
if (publish_all) {
|
||||
// Publish all templates in subdirectories
|
||||
maketitle("Publishing all templates");
|
||||
info << "Parent directory: " << target_dir << std::endl;
|
||||
info << "Registry: " << selected_registry->name << " (" << server_url << ")" << 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 " << template_dir << std::endl;
|
||||
return 1;
|
||||
}
|
||||
info << " ✓ Template is valid" << std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
// Step 2: Calculate directory hash (for unpacked content verification)
|
||||
info << "=== Calculating Hash ===" << std::endl;
|
||||
std::string unpacked_hash = hash_directory_recursive(template_dir);
|
||||
if (unpacked_hash.empty()) {
|
||||
error << "Failed to calculate directory hash" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
info << " Hash: " << unpacked_hash << std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
// Step 3: Create temp directory and tarball
|
||||
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);
|
||||
// Find all subdirectories that are valid templates
|
||||
std::vector<std::filesystem::path> template_dirs;
|
||||
for (const auto& entry : std::filesystem::directory_iterator(dir_path)) {
|
||||
if (entry.is_directory() && is_valid_template_dir(entry.path())) {
|
||||
template_dirs.push_back(entry.path());
|
||||
}
|
||||
}
|
||||
} cleaner{temp_dir};
|
||||
|
||||
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;
|
||||
// Sort alphabetically
|
||||
std::sort(template_dirs.begin(), template_dirs.end());
|
||||
|
||||
if (template_dirs.empty()) {
|
||||
error << "No valid templates found in " << target_dir << std::endl;
|
||||
info << "Templates must have config/.template_info.env" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
info << "Found " << template_dirs.size() << " template(s) to publish:" << std::endl;
|
||||
for (const auto& tdir : template_dirs) {
|
||||
info << " - " << tdir.filename().string() << std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
|
||||
int success_count = 0;
|
||||
int fail_count = 0;
|
||||
std::vector<std::string> failed_templates;
|
||||
|
||||
for (const auto& tdir : template_dirs) {
|
||||
int result = publish_single_template(tdir.string(), server_url, effective_token, false);
|
||||
if (result == 0) {
|
||||
success_count++;
|
||||
} else {
|
||||
fail_count++;
|
||||
failed_templates.push_back(tdir.filename().string());
|
||||
}
|
||||
}
|
||||
|
||||
// Summary
|
||||
std::cout << std::endl;
|
||||
maketitle("Publish Summary");
|
||||
info << "Successfully published: " << success_count << " template(s)" << std::endl;
|
||||
if (fail_count > 0) {
|
||||
error << "Failed to publish: " << fail_count << " template(s)" << std::endl;
|
||||
for (const auto& name : failed_templates) {
|
||||
error << " - " << name << std::endl;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
} else {
|
||||
// Single template publish
|
||||
info << "Registry: " << selected_registry->name << " (" << server_url << ")" << std::endl;
|
||||
|
||||
int result = publish_single_template(target_dir, server_url, effective_token, false);
|
||||
|
||||
if (result == 0) {
|
||||
std::string template_name = dir_path.filename().string();
|
||||
maketitle("Publish Complete");
|
||||
info << "Template '" << template_name << "' published successfully!" << std::endl;
|
||||
info << "Tags: " << get_date_tag() << ", latest" << std::endl;
|
||||
info << "URL: " << server_url << "/" << template_name << ":latest" << std::endl;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
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 4: Publish with tags
|
||||
info << "=== Publishing to Registry ===" << std::endl;
|
||||
|
||||
std::string date_tag = get_date_tag();
|
||||
std::vector<std::string> labeltags = {
|
||||
template_name + ":" + date_tag,
|
||||
template_name + ":latest"
|
||||
};
|
||||
|
||||
info << " Tags: " << date_tag << ", latest" << std::endl;
|
||||
info << " Uploading..." << std::endl;
|
||||
|
||||
if (!publish_to_registry(server_url, effective_token, tarball_path, labeltags, unpacked_hash)) {
|
||||
error << "Failed to publish template" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
info << " ✓ Published successfully" << 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: " << server_url << "/" << template_name << ":latest" << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace dropshell
|
||||
|
||||
@@ -254,8 +254,8 @@ int validate_handler(const CommandContext& ctx) {
|
||||
info << "✓ Template '" << template_name << "' passed all checks!" << std::endl;
|
||||
return 0;
|
||||
} else if (errors == 0) {
|
||||
warning << "Template '" << template_name << "' has " << warnings << " warning(s)" << std::endl;
|
||||
return 0; // Warnings don't fail validation
|
||||
error << "Template '" << template_name << "' has " << warnings << " warning(s)" << std::endl;
|
||||
return 1; // Warnings fail validation
|
||||
} else {
|
||||
error << "Template '" << template_name << "' has " << errors << " error(s) and " << warnings << " warning(s)" << std::endl;
|
||||
return 1;
|
||||
|
||||
Reference in New Issue
Block a user