feat: add --features and --config flags to kappa add
CI / build-and-test (push) Successful in 49s

kappa add nginx --features "ssl=on,gzip=off" --config "port=8080"
adds/updates feature flags and config values in the system config.
Comma-separated key=value pairs. Idempotent — only prints 'updated'
when something actually changed.
This commit is contained in:
2026-08-05 07:34:12 -04:00
parent 7ba3fe6632
commit e69ebf220d
+74 -5
View File
@@ -54,6 +54,8 @@ Subcommands:
rollback Show available generations
index <dir> Build an index.kap from .kap files in a directory
add <pkg> [v] Add a package to system config (optional version)
--features "key=on,key2=off" Set feature flags
--config "key=val,key2=val2" Set config values
remove <pkg> Remove a package from system config
Options:
@@ -232,6 +234,8 @@ int main(int argc, char* argv[]) {
const char* file_arg = nullptr;
const char* version_arg = nullptr;
const char* features_arg = nullptr;
const char* config_arg = nullptr;
bool dry_run = false;
for (int i = arg_start + 1; i < argc; ++i) {
if (std::string_view(argv[i]) == "-h"
@@ -254,6 +258,14 @@ int main(int argc, char* argv[]) {
paths::set_root(argv[++i]);
continue;
}
if (std::string_view(argv[i]) == "--features" && i + 1 < argc) {
features_arg = argv[++i];
continue;
}
if (std::string_view(argv[i]) == "--config" && i + 1 < argc) {
config_arg = argv[++i];
continue;
}
if (std::string_view(argv[i]) == "--dry-run") {
dry_run = true;
continue;
@@ -334,6 +346,52 @@ int main(int argc, char* argv[]) {
std::cerr << "error: no package name specified\n";
return 1;
}
// Parse --features key=on,key2=off
std::unordered_map<std::string, dsl::FeatureDef> features;
if (features_arg != nullptr) {
std::string_view fsv(features_arg);
std::size_t pos = 0;
while (pos < fsv.size()) {
auto comma = fsv.find(',', pos);
auto pair = fsv.substr(pos, comma == std::string_view::npos
? std::string_view::npos
: comma - pos);
auto eq = pair.find('=');
if (eq != std::string_view::npos) {
auto key = std::string(pair.substr(0, eq));
auto val = pair.substr(eq + 1);
while (!key.empty() && key.back() == ' ') key.pop_back();
while (!val.empty() && val.front() == ' ') val.remove_prefix(1);
features[key] = {val == "on" || val == "true" || val == "1",
false, ""};
}
pos = comma == std::string_view::npos ? fsv.size() : comma + 1;
}
}
// Parse --config key=val,key2=val2
std::unordered_map<std::string, std::string> config;
if (config_arg != nullptr) {
std::string_view csv(config_arg);
std::size_t pos = 0;
while (pos < csv.size()) {
auto comma = csv.find(',', pos);
auto pair = csv.substr(pos, comma == std::string_view::npos
? std::string_view::npos
: comma - pos);
auto eq = pair.find('=');
if (eq != std::string_view::npos) {
auto key = std::string(pair.substr(0, eq));
auto val = std::string(pair.substr(eq + 1));
while (!key.empty() && key.back() == ' ') key.pop_back();
while (!val.empty() && val.front() == ' ') val.erase(0, 1);
config[key] = val;
}
pos = comma == std::string_view::npos ? csv.size() : comma + 1;
}
}
try {
auto config_path = std::filesystem::path(paths::system_dir()) / "config.kap";
dsl::SystemConfig cfg;
@@ -347,10 +405,21 @@ int main(int argc, char* argv[]) {
for (auto& p : cfg.packages) {
if (p.name == file_arg) {
found = true;
bool changed = false;
if (version_arg != nullptr) {
p.version = version_arg;
std::cout << "updated " << file_arg
<< " → version " << version_arg << "\n";
changed = true;
}
if (!features.empty()) {
for (auto& [k, v] : features) { p.features[k] = v; changed = true; }
}
if (!config.empty()) {
for (auto& [k, v] : config) { p.config[k] = v; changed = true; }
}
if (changed) {
std::cout << "updated " << file_arg;
if (version_arg != nullptr) std::cout << " → " << version_arg;
std::cout << "\n";
} else {
std::cout << file_arg << " already in packages\n";
}
@@ -364,11 +433,11 @@ int main(int argc, char* argv[]) {
if (version_arg != nullptr) {
pref.version = version_arg;
}
pref.features = std::move(features);
pref.config = std::move(config);
cfg.packages.push_back(std::move(pref));
std::cout << "added " << file_arg;
if (version_arg != nullptr) {
std::cout << " " << version_arg;
}
if (version_arg != nullptr) std::cout << " " << version_arg;
std::cout << "\n";
}