feat: Update 2 files
This commit is contained in:
@@ -13,6 +13,7 @@
|
|||||||
#include <ctime>
|
#include <ctime>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
namespace dropshell {
|
namespace dropshell {
|
||||||
|
|
||||||
@@ -31,9 +32,9 @@ struct PublishTemplateCommandRegister {
|
|||||||
false, // hidden
|
false, // hidden
|
||||||
true, // requires_config
|
true, // requires_config
|
||||||
true, // requires_install
|
true, // requires_install
|
||||||
1, // min_args (directory required)
|
1, // min_args
|
||||||
2, // max_args (optional registry name + directory)
|
3, // max_args (--all + optional registry + directory)
|
||||||
"publish-template [REGISTRY] DIRECTORY",
|
"publish-template [--all] [REGISTRY] DIRECTORY",
|
||||||
"Publish a template to a template registry.",
|
"Publish a template to a template registry.",
|
||||||
R"HELP(
|
R"HELP(
|
||||||
Publishes a template directory to a template registry.
|
Publishes a template directory to a template registry.
|
||||||
@@ -41,11 +42,15 @@ Publishes a template directory to a template registry.
|
|||||||
Usage:
|
Usage:
|
||||||
ds publish-template DIRECTORY
|
ds publish-template DIRECTORY
|
||||||
ds publish-template REGISTRY_NAME DIRECTORY
|
ds publish-template REGISTRY_NAME DIRECTORY
|
||||||
|
ds publish-template --all DIRECTORY
|
||||||
|
ds publish-template --all REGISTRY_NAME DIRECTORY
|
||||||
|
|
||||||
Arguments:
|
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.
|
REGISTRY_NAME Optional. Name of the template registry to publish to.
|
||||||
If not specified, uses the first registry with a token.
|
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:
|
The template is validated before publishing. Two tags are created:
|
||||||
- YYYYMMDD (e.g., 20251228)
|
- YYYYMMDD (e.g., 20251228)
|
||||||
@@ -64,6 +69,7 @@ Requirements:
|
|||||||
Example:
|
Example:
|
||||||
ds publish-template ./my-template
|
ds publish-template ./my-template
|
||||||
ds publish-template main ./my-template
|
ds publish-template main ./my-template
|
||||||
|
ds publish-template --all ./templates-dir
|
||||||
SOS_WRITE_TOKEN=xxx ds publish-template ./my-template
|
SOS_WRITE_TOKEN=xxx ds publish-template ./my-template
|
||||||
)HELP"
|
)HELP"
|
||||||
});
|
});
|
||||||
@@ -73,6 +79,7 @@ Example:
|
|||||||
|
|
||||||
void publish_template_autocomplete(const CommandContext& ctx) {
|
void publish_template_autocomplete(const CommandContext& ctx) {
|
||||||
if (ctx.args.size() == 0) {
|
if (ctx.args.size() == 0) {
|
||||||
|
rawout << "--all" << std::endl;
|
||||||
// List registry names
|
// List registry names
|
||||||
std::vector<tRegistryEntry> registries = gConfig().get_template_registry_urls();
|
std::vector<tRegistryEntry> registries = gConfig().get_template_registry_urls();
|
||||||
for (const auto& reg : registries) {
|
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) {
|
// Check if a directory is a valid template (has config/.template_info.env)
|
||||||
std::string registry_name;
|
static bool is_valid_template_dir(const std::filesystem::path& dir_path) {
|
||||||
std::string template_dir;
|
return std::filesystem::exists(dir_path / "config" / ".template_info.env");
|
||||||
|
}
|
||||||
|
|
||||||
// Parse arguments
|
// Publish a single template directory
|
||||||
if (ctx.args.size() == 1) {
|
// Returns: 0 = success, 1 = error, 2 = skipped (not a template)
|
||||||
// Only directory provided
|
static int publish_single_template(const std::string& template_dir, const std::string& server_url,
|
||||||
template_dir = ctx.args[0];
|
const std::string& token, bool quiet = false) {
|
||||||
} else if (ctx.args.size() == 2) {
|
std::filesystem::path dir_path(template_dir);
|
||||||
// Registry name and directory provided
|
std::string template_name = dir_path.filename().string();
|
||||||
registry_name = ctx.args[0];
|
|
||||||
template_dir = ctx.args[1];
|
if (!quiet) {
|
||||||
} else {
|
maketitle("Publishing template: " + template_name);
|
||||||
error << "Usage: ds publish-template [REGISTRY] DIRECTORY" << std::endl;
|
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;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve template directory to absolute path
|
auto file_size = std::filesystem::file_size(tarball_path);
|
||||||
std::filesystem::path dir_path(template_dir);
|
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()) {
|
if (dir_path.is_relative()) {
|
||||||
dir_path = std::filesystem::current_path() / dir_path;
|
dir_path = std::filesystem::current_path() / dir_path;
|
||||||
}
|
}
|
||||||
dir_path = std::filesystem::canonical(dir_path);
|
dir_path = std::filesystem::canonical(dir_path);
|
||||||
template_dir = dir_path.string();
|
target_dir = dir_path.string();
|
||||||
|
|
||||||
// Check directory exists
|
// Check directory exists
|
||||||
if (!std::filesystem::exists(dir_path) || !std::filesystem::is_directory(dir_path)) {
|
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;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string template_name = dir_path.filename().string();
|
|
||||||
|
|
||||||
// Find registry and token
|
// Find registry and token
|
||||||
std::vector<tRegistryEntry> registries = gConfig().get_template_registry_urls();
|
std::vector<tRegistryEntry> registries = gConfig().get_template_registry_urls();
|
||||||
tRegistryEntry* selected_registry = nullptr;
|
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()) {
|
if (registry_name.empty()) {
|
||||||
// Find first registry with a token
|
// Find first registry with a token
|
||||||
@@ -367,81 +471,79 @@ int publish_template_handler(const CommandContext& ctx) {
|
|||||||
server_url.pop_back();
|
server_url.pop_back();
|
||||||
}
|
}
|
||||||
|
|
||||||
maketitle("Publishing template: " + template_name);
|
if (publish_all) {
|
||||||
info << "Directory: " << template_dir << std::endl;
|
// 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;
|
info << "Registry: " << selected_registry->name << " (" << server_url << ")" << std::endl;
|
||||||
std::cout << std::endl;
|
std::cout << std::endl;
|
||||||
|
|
||||||
// Step 1: Validate template
|
// Find all subdirectories that are valid templates
|
||||||
info << "=== Validating Template ===" << std::endl;
|
std::vector<std::filesystem::path> template_dirs;
|
||||||
if (!template_manager::test_template(template_dir)) {
|
for (const auto& entry : std::filesystem::directory_iterator(dir_path)) {
|
||||||
error << "Template validation failed. Please fix issues before publishing." << std::endl;
|
if (entry.is_directory() && is_valid_template_dir(entry.path())) {
|
||||||
info << "Run: ds validate-template " << template_dir << std::endl;
|
template_dirs.push_back(entry.path());
|
||||||
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
|
// Sort alphabetically
|
||||||
std::string temp_dir = std::filesystem::temp_directory_path().string() + "/dropshell-publish-" + std::to_string(getpid());
|
std::sort(template_dirs.begin(), template_dirs.end());
|
||||||
std::filesystem::create_directories(temp_dir);
|
|
||||||
|
|
||||||
// Cleanup on exit
|
if (template_dirs.empty()) {
|
||||||
struct TempDirCleaner {
|
error << "No valid templates found in " << target_dir << std::endl;
|
||||||
std::string path;
|
info << "Templates must have config/.template_info.env" << std::endl;
|
||||||
~TempDirCleaner() {
|
|
||||||
std::filesystem::remove_all(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;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto file_size = std::filesystem::file_size(tarball_path);
|
info << "Found " << template_dirs.size() << " template(s) to publish:" << std::endl;
|
||||||
info << " Created " << template_name << ".tgz (" << (file_size / 1024) << " KB)" << std::endl;
|
for (const auto& tdir : template_dirs) {
|
||||||
std::cout << std::endl;
|
info << " - " << tdir.filename().string() << 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;
|
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
|
// Summary
|
||||||
maketitle("Publish Complete");
|
std::cout << std::endl;
|
||||||
info << "Template '" << template_name << "' published successfully!" << std::endl;
|
maketitle("Publish Summary");
|
||||||
info << "Tags: " << date_tag << ", latest" << std::endl;
|
info << "Successfully published: " << success_count << " template(s)" << std::endl;
|
||||||
info << "URL: " << server_url << "/" << template_name << ":latest" << 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;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace dropshell
|
} // namespace dropshell
|
||||||
|
|||||||
@@ -254,8 +254,8 @@ int validate_handler(const CommandContext& ctx) {
|
|||||||
info << "✓ Template '" << template_name << "' passed all checks!" << std::endl;
|
info << "✓ Template '" << template_name << "' passed all checks!" << std::endl;
|
||||||
return 0;
|
return 0;
|
||||||
} else if (errors == 0) {
|
} else if (errors == 0) {
|
||||||
warning << "Template '" << template_name << "' has " << warnings << " warning(s)" << std::endl;
|
error << "Template '" << template_name << "' has " << warnings << " warning(s)" << std::endl;
|
||||||
return 0; // Warnings don't fail validation
|
return 1; // Warnings fail validation
|
||||||
} else {
|
} else {
|
||||||
error << "Template '" << template_name << "' has " << errors << " error(s) and " << warnings << " warning(s)" << std::endl;
|
error << "Template '" << template_name << "' has " << errors << " error(s) and " << warnings << " warning(s)" << std::endl;
|
||||||
return 1;
|
return 1;
|
||||||
|
|||||||
Reference in New Issue
Block a user