#include "./config.h" #include "../librepomgr/buildactions/buildaction.h" #include "../librepomgr/buildactions/buildactionmeta.h" #include "../librepomgr/json.h" #include "../librepomgr/webapi/params.h" #include "../librepomgr/webclient/session.h" #include "../libpkg/data/database.h" #include "../libpkg/data/package.h" #include "resources/config.h" #include #include #include #include #include #include #include #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wsign-conversion" #pragma GCC diagnostic ignored "-Wconversion" #pragma GCC diagnostic ignored "-Wshadow=compatible-local" #endif #include #ifdef __GNUC__ #pragma GCC diagnostic pop #endif #include #include #include #include #include using namespace CppUtilities; using namespace CppUtilities::EscapeCodes; using namespace std; // helpers for formatting output static void configureColumnWidths(tabulate::Table &table) { const auto terminalSize = determineTerminalSize(); if (!terminalSize.columns) { return; } struct ColumnStats { std::size_t maxSize = 0; std::size_t totalSize = 0; std::size_t rows = 0; double averageSize = 0.0; double averagePercentage = 0.0; std::size_t width = 0; }; auto columnStats = std::vector(); for (const auto &row : table) { const auto columnCount = row.size(); if (columnStats.size() < columnCount) { columnStats.resize(columnCount); } auto column = columnStats.begin(); for (const auto &cell : row.cells()) { const auto size = cell->size(); column->maxSize = std::max(column->maxSize, size); column->totalSize += std::max(10, size); column->rows += 1; ++column; } } auto totalAverageSize = 0.0; for (auto &column : columnStats) { totalAverageSize += (column.averageSize = static_cast(column.totalSize) / static_cast(column.rows)); } for (auto &column : columnStats) { column.averagePercentage = column.averageSize / totalAverageSize; column.width = std::max(static_cast(static_cast(terminalSize.columns) * column.averagePercentage), std::min(column.maxSize, 10u)); } for (std::size_t columnIndex = 0; columnIndex != columnStats.size(); ++columnIndex) { table.column(columnIndex).format().width(columnStats[columnIndex].width); } } static void printPackageSearchResults(const LibRepoMgr::WebClient::Response::body_type::value_type &jsonData) { auto errors = ReflectiveRapidJSON::JsonDeserializationErrors(); errors.throwOn = ReflectiveRapidJSON::JsonDeserializationErrors::ThrowOn::All; const auto packages = ReflectiveRapidJSON::JsonReflector::fromJson>(jsonData.data(), jsonData.size(), &errors); tabulate::Table t; t.format().hide_border(); t.add_row({ "Arch", "Repo", "Name", "Version", "Description", "Build date" }); for (const auto &[db, package] : packages) { const auto &dbInfo = std::get(db); t.add_row( { package->packageInfo ? package->packageInfo->arch : dbInfo.arch, dbInfo.name, package->name, package->version, package->description, package->packageInfo && !package->packageInfo->buildDate.isNull() ? package->packageInfo->buildDate.toString() : "?" }); } t.row(0).format().font_align(tabulate::FontAlign::center).font_style({ tabulate::FontStyle::bold }); configureColumnWidths(t); std::cout << t << std::endl; } template inline std::string formatList(const List &list) { return joinStrings(list, ", "); } static std::string formatDependencies(const std::vector &deps) { auto asStrings = std::vector(); asStrings.reserve(deps.size()); for (const auto &dep : deps) { asStrings.emplace_back(dep.toString()); } return formatList(asStrings); } static void printPackageDetails(const LibRepoMgr::WebClient::Response::body_type::value_type &jsonData) { auto errors = ReflectiveRapidJSON::JsonDeserializationErrors(); errors.throwOn = ReflectiveRapidJSON::JsonDeserializationErrors::ThrowOn::All; const auto packages = ReflectiveRapidJSON::JsonReflector::fromJson>(jsonData.data(), jsonData.size(), &errors); for (const auto &package : packages) { const auto *const pkg = &package; std::cout << TextAttribute::Bold << pkg->name << ' ' << pkg->version << TextAttribute::Reset << '\n'; tabulate::Table t; t.format().hide_border(); if (pkg->packageInfo) { t.add_row({ "Arch", pkg->packageInfo->arch }); } else if (pkg->sourceInfo) { t.add_row({ "Archs", formatList(pkg->sourceInfo->archs) }); } t.add_row({ "Description", pkg->description }); t.add_row({ "Upstream URL", pkg->upstreamUrl }); t.add_row({ "License(s)", formatList(pkg->licenses) }); t.add_row({ "Groups", formatList(pkg->groups) }); if (pkg->packageInfo && pkg->packageInfo->size) { t.add_row({ "Package size", dataSizeToString(pkg->packageInfo->size, true) }); } if (pkg->installInfo) { t.add_row({ "Installed size", dataSizeToString(pkg->installInfo->installedSize, true) }); } if (pkg->packageInfo) { if (!pkg->packageInfo->packager.empty()) { t.add_row({ "Packager", pkg->packageInfo->packager }); } if (!pkg->packageInfo->buildDate.isNull()) { t.add_row({ "Packager", pkg->packageInfo->buildDate.toString() }); } } t.add_row({ "Dependencies", formatDependencies(pkg->dependencies) }); t.add_row({ "Optional dependencies", formatDependencies(pkg->optionalDependencies) }); if (pkg->sourceInfo) { t.add_row({ "Make dependencies", formatDependencies(pkg->sourceInfo->makeDependencies) }); t.add_row({ "Check dependencies", formatDependencies(pkg->sourceInfo->checkDependencies) }); } t.add_row({ "Provides", formatDependencies(pkg->provides) }); t.add_row({ "Replaces", formatDependencies(pkg->replaces) }); t.add_row({ "Conflicts", formatDependencies(pkg->conflicts) }); t.add_row({ "Contained libraries", formatList(pkg->libprovides) }); t.add_row({ "Needed libraries", formatList(pkg->libdepends) }); t.column(0).format().font_align(tabulate::FontAlign::right); configureColumnWidths(t); std::cout << t << '\n'; } std::cout.flush(); } static void printListOfBuildActions(const LibRepoMgr::WebClient::Response::body_type::value_type &jsonData) { auto errors = ReflectiveRapidJSON::JsonDeserializationErrors(); errors.throwOn = ReflectiveRapidJSON::JsonDeserializationErrors::ThrowOn::All; auto actions = ReflectiveRapidJSON::JsonReflector::fromJson>(jsonData.data(), jsonData.size(), &errors); actions.sort([](const auto &lhs, const auto &rhs) { return lhs.created < rhs.created; }); const auto meta = LibRepoMgr::BuildActionMetaInfo(); const auto unknown = std::string_view("?"); auto t = tabulate::Table(); t.format().hide_border(); t.add_row( { "ID", "Task", "Type", "Status", "Result", "Created", "Started", "Runtime", "Directory", "Source repo", "Destination repo", "Packages" }); for (const auto &a : actions) { const LibRepoMgr::BuildActionTypeMetaInfo *typeInfo = nullptr; if (meta.isTypeIdValid(a.type)) { typeInfo = &meta.typeInfoForId(a.type); } const auto status = static_cast(a.status) < meta.states.size() ? meta.states[static_cast(a.status)].name : unknown; const auto result = static_cast(a.result) < meta.results.size() ? meta.results[static_cast(a.result)].name : unknown; t.add_row({ numberToString(a.id), a.taskName, (typeInfo ? typeInfo->name : unknown).data(), status.data(), result.data(), a.created.toString(DateTimeOutputFormat::DateAndTime, true), a.started.toString(DateTimeOutputFormat::DateAndTime, true), (a.finished - a.started).toString(TimeSpanOutputFormat::WithMeasures, true), a.directory, joinStrings(a.sourceDbs, ", "), joinStrings(a.destinationDbs, ", "), joinStrings(a.packageNames, ", ") }); } t.row(0).format().font_align(tabulate::FontAlign::center).font_style({ tabulate::FontStyle::bold }); configureColumnWidths(t); std::cout << t << std::endl; } static std::string formatTimeStamp(const DateTime timeStamp) { static const auto now = DateTime::gmtNow(); return (now - timeStamp).toString(TimeSpanOutputFormat::WithMeasures, true) % " ago (" % timeStamp.toString(DateTimeOutputFormat::DateAndTime, true) + ')'; } static tabulate::Table printListOfStringsAsSubTable(const std::vector &strings) { auto t = tabulate::Table(); t.format().hide_border(); for (const auto &string : strings) { t.add_row({ string }); } return t; } static void printBuildAction(const LibRepoMgr::BuildAction &a, const LibRepoMgr::BuildActionMetaInfo &meta) { constexpr auto unknown = std::string_view("?"); const auto typeInfo = meta.isTypeIdValid(a.type) ? &meta.typeInfoForId(a.type) : nullptr; const auto status = static_cast(a.status) < meta.states.size() ? meta.states[static_cast(a.status)].name : unknown; const auto result = static_cast(a.result) < meta.results.size() ? meta.results[static_cast(a.result)].name : unknown; const auto flags = typeInfo ? &typeInfo->flags : nullptr; const auto startAfter = a.startAfter | std::views::transform([](auto id) { return numberToString(id); }); std::cout << TextAttribute::Bold << "Build action " << a.id << TextAttribute::Reset << '\n'; tabulate::Table t; t.format().hide_border(); t.add_row({ "Task", a.taskName }); t.add_row({ "Type", (typeInfo ? typeInfo->name : unknown).data() }); t.add_row({ "Status", status.data() }); t.add_row({ "Result", result.data() }); if (std::holds_alternative(a.resultData)) { t.add_row({ "Result data", std::get(a.resultData) }); } else { t.add_row({ "Result data", "(no output formatter for result output type implemented yet)" }); } t.add_row({ "Created", formatTimeStamp(a.created) }); t.add_row({ "Started", formatTimeStamp(a.started) }); t.add_row({ "Finished", formatTimeStamp(a.finished) }); t.add_row({ "Start after", joinStrings(startAfter, ", ") }); t.add_row({ "Directory", a.directory }); t.add_row({ "Source repo", printListOfStringsAsSubTable(a.sourceDbs) }); t.add_row({ "Destination repo", printListOfStringsAsSubTable(a.destinationDbs) }); t.add_row({ "Packages", printListOfStringsAsSubTable(a.packageNames) }); if (flags) { auto presentFlags = std::string(); presentFlags.reserve(32); for (const auto &flag : *flags) { if (a.flags & flag.id) { if (!presentFlags.empty()) { presentFlags += ", "; } presentFlags += flag.name; } } t.add_row({ "Flags", std::move(presentFlags) }); } else { t.add_row({ "Flags", numberToString(a.flags) }); } t.add_row({ "Log files", printListOfStringsAsSubTable(a.logfiles) }); t.add_row({ "Artefacts", printListOfStringsAsSubTable(a.artefacts) }); t.add_row({ "Output", a.output }); t.column(0).format().font_align(tabulate::FontAlign::right); std::cout << t << '\n'; } static void printBuildAction(const LibRepoMgr::WebClient::Response::body_type::value_type &jsonData) { auto buildAction = LibRepoMgr::BuildAction(); { const auto doc = ReflectiveRapidJSON::JsonReflector::parseJsonDocFromString(jsonData.data(), jsonData.size()); auto errors = ReflectiveRapidJSON::JsonDeserializationErrors(); errors.throwOn = ReflectiveRapidJSON::JsonDeserializationErrors::ThrowOn::All; ReflectiveRapidJSON::JsonReflector::pull(buildAction, doc.GetObject(), &errors); } printBuildAction(buildAction, LibRepoMgr::BuildActionMetaInfo()); } static void printBuildActions(const LibRepoMgr::WebClient::Response::body_type::value_type &jsonData) { auto errors = ReflectiveRapidJSON::JsonDeserializationErrors(); errors.throwOn = ReflectiveRapidJSON::JsonDeserializationErrors::ThrowOn::All; auto buildActions = ReflectiveRapidJSON::JsonReflector::fromJson>(jsonData.data(), jsonData.size(), &errors); buildActions.sort([](const auto &lhs, const auto &rhs) { return lhs.created < rhs.created; }); const auto meta = LibRepoMgr::BuildActionMetaInfo(); for (const auto &a : buildActions) { printBuildAction(a, meta); } std::cout.flush(); } static void printRawDataForErrorHandling(const LibRepoMgr::WebClient::Response::body_type::value_type &rawData) { if (!rawData.empty()) { std::cerr << Phrases::InfoMessage << "Server replied:"; if (rawData.size() > 50 || rawData.find('\n') != std::string::npos) { std::cerr << Phrases::End; } else { std::cerr << TextAttribute::Reset << ' '; } std::cerr << rawData << '\n'; } } static void printRawData(const LibRepoMgr::WebClient::Response::body_type::value_type &rawData) { std::cout << rawData; } static void handleResponse(const std::string &url, LibRepoMgr::WebClient::Session &session, const LibRepoMgr::WebClient::HttpClientError &error, void (*printer)(const LibRepoMgr::WebClient::Response::body_type::value_type &jsonData), int &returnCode) { auto result = boost::beast::http::status::ok; auto body = std::optional(); if (auto *const responseParser = std::get_if(&session.response)) { result = responseParser->get().result(); body = std::move(responseParser->get().body()); } else if (auto *const response = std::get_if(&session.response)) { result = response->result(); body = std::move(response->body()); } if (error.errorCode != boost::beast::errc::success && error.errorCode != boost::asio::ssl::error::stream_truncated) { std::cerr << Phrases::ErrorMessage << "Unable to connect: " << error.what() << Phrases::End; returnCode = 9; } if (result != boost::beast::http::status::ok) { std::cerr << Phrases::ErrorMessage << "HTTP request not successful: " << result << " (" << static_cast>(result) << " response)" << Phrases::End; returnCode = 10; } if (body.has_value()) { if (returnCode) { printRawDataForErrorHandling(body.value()); } else { try { std::invoke(printer, body.value()); } catch (const ReflectiveRapidJSON::JsonDeserializationError &e) { std::cerr << Phrases::ErrorMessage << "Unable to make sense of response: " << ReflectiveRapidJSON::formatJsonDeserializationError(e) << Phrases::End; returnCode = 13; } catch (const RAPIDJSON_NAMESPACE::ParseResult &e) { std::cerr << Phrases::ErrorMessage << "Unable to parse responnse: " << tupleToString(LibRepoMgr::serializeParseError(e)) << Phrases::End; returnCode = 11; } catch (const std::runtime_error &e) { std::cerr << Phrases::ErrorMessage << "Unable to display response: " << e.what() << Phrases::End; returnCode = 12; } } } if (returnCode) { std::cerr << Phrases::InfoMessage << "URL was: " << url << std::endl; } } static void printChunk(const boost::beast::http::chunk_extensions &chunkExtensions, std::string_view chunkData) { CPP_UTILITIES_UNUSED(chunkExtensions) std::cout << chunkData; } // helper for turning CLI args into URL query parameters static std::string asQueryParam(const Argument &cliArg, std::string_view paramName = std::string_view()) { if (!cliArg.isPresent()) { return std::string(); } const auto argValues = cliArg.values(0) | std::views::transform([](const char *argValue) { return LibRepoMgr::WebAPI::Url::encodeValue(argValue); }); return joinStrings( argValues, "&", true, argsToString(paramName.empty() ? std::string_view(cliArg.name()) : paramName, '=')); } static void appendAsQueryParam(std::string &path, const Argument &cliArg, std::string_view paramName = std::string_view()) { auto asParam = asQueryParam(cliArg, paramName); if (asParam.empty()) { return; } if (!(path.empty() || path.ends_with('?'))) { path += '&'; } path += std::move(asParam); } static void appendAsQueryParam(std::string &path, std::initializer_list args) { for (const auto *arg : args) { appendAsQueryParam(path, *arg, arg->name()); } } int main(int argc, const char *argv[]) { // define command-specific parameters auto verb = boost::beast::http::verb::get; auto path = std::string(); void (*chunkHandler)(const boost::beast::http::chunk_extensions &chunkExtensions, std::string_view chunkData) = nullptr; void (*printer)(const LibRepoMgr::WebClient::Response::body_type::value_type &jsonData) = nullptr; // read CLI args auto parser = ArgumentParser(); auto configFileArg = ConfigValueArgument("config-file", 'c', "specifies the path of the config file", { "path" }); configFileArg.setEnvironmentVariable(PROJECT_VARNAME_UPPER "_CONFIG_FILE"); auto instanceArg = ConfigValueArgument("instance", 'i', "specifies the instance to connect to", { "instance" }); auto rawArg = ConfigValueArgument("raw", 'r', "print the raw output from the server"); auto verboseArg = ConfigValueArgument("verbose", 'v', "prints debugging output"); auto packageArg = OperationArgument("package", 'p', "Package-related operations:"); auto searchArg = OperationArgument("search", 's', "searches for packages"); auto searchTermArg = ConfigValueArgument("term", 't', "specifies the search term", { "term" }); searchTermArg.setImplicit(true); searchTermArg.setRequired(true); auto searchModeArg = ConfigValueArgument("mode", 'm', "specifies the mode", { "name/name-contains/regex/provides/depends/libprovides/libdepends" }); searchModeArg.setPreDefinedCompletionValues("name name-contains regex provides depends libprovides libdepends"); searchArg.setSubArguments({ &searchTermArg, &searchModeArg }); searchArg.setCallback([&path, &printer, &searchTermArg, &searchModeArg](const ArgumentOccurrence &) { path = "/api/v0/packages?mode=" + LibRepoMgr::WebAPI::Url::encodeValue(searchModeArg.firstValueOr("name-contains")); printer = printPackageSearchResults; appendAsQueryParam(path, searchTermArg, "name"); }); auto packageNameArg = ConfigValueArgument("name", 'n', "specifies the package name", { "name" }); packageNameArg.setImplicit(true); packageNameArg.setRequired(true); auto packageShowArg = OperationArgument("show", 'd', "shows details about a package"); packageShowArg.setSubArguments({ &packageNameArg }); packageShowArg.setCallback([&path, &printer, &packageNameArg](const ArgumentOccurrence &) { path = "/api/v0/packages?mode=name&details=1"; printer = printPackageDetails; appendAsQueryParam(path, packageNameArg, "name"); }); packageArg.setSubArguments({ &searchArg, &packageShowArg }); auto actionArg = OperationArgument("action", 'a', "Build-action-related operations:"); auto listActionsArg = OperationArgument("list", 'l', "list build actions"); listActionsArg.setCallback([&path, &printer](const ArgumentOccurrence &) { path = "/api/v0/build-action"; printer = printListOfBuildActions; }); auto buildActionIdArg = ConfigValueArgument("id", 'i', "specifies the build action IDs", { "ID" }); buildActionIdArg.setImplicit(true); buildActionIdArg.setRequired(true); buildActionIdArg.setRequiredValueCount(Argument::varValueCount); auto showBuildActionArg = OperationArgument("show", 'd', "show details about a build action"); showBuildActionArg.setCallback([&path, &printer, &buildActionIdArg](const ArgumentOccurrence &) { path = "/api/v0/build-action/details?"; printer = printBuildActions; appendAsQueryParam(path, buildActionIdArg, "id"); }); showBuildActionArg.setSubArguments({ &buildActionIdArg }); auto singleBuildActionIdArg = ConfigValueArgument("id", 'i', "specifies the build action ID", { "ID" }); singleBuildActionIdArg.setImplicit(true); singleBuildActionIdArg.setRequired(true); auto streamOutputBuildActionArg = OperationArgument("output", 'o', "stream overall build action output"); streamOutputBuildActionArg.setCallback([&path, &printer, &chunkHandler, &singleBuildActionIdArg](const ArgumentOccurrence &) { path = "/api/v0/build-action/output?"; printer = printRawData; chunkHandler = printChunk; appendAsQueryParam(path, singleBuildActionIdArg, "id"); }); streamOutputBuildActionArg.setSubArguments({ &singleBuildActionIdArg }); auto streamLogfileBuildActionArg = OperationArgument("logfile", 'f', "stream build action logfile"); auto buildActionFilePathArg = ConfigValueArgument("path", 'p', "specifies the file path", { "path" }); buildActionFilePathArg.setRequired(true); streamLogfileBuildActionArg.setCallback( [&path, &printer, &chunkHandler, &singleBuildActionIdArg, &buildActionFilePathArg](const ArgumentOccurrence &) { path = "/api/v0/build-action/logfile?"; printer = printRawData; chunkHandler = printChunk; appendAsQueryParam(path, singleBuildActionIdArg, "id"); appendAsQueryParam(path, buildActionFilePathArg, "name"); }); streamLogfileBuildActionArg.setSubArguments({ &singleBuildActionIdArg, &buildActionFilePathArg }); auto streamArtefactBuildActionArg = OperationArgument("artefact", 'a', "stream build action artefact"); streamArtefactBuildActionArg.setCallback( [&path, &printer, &chunkHandler, &singleBuildActionIdArg, &buildActionFilePathArg](const ArgumentOccurrence &) { path = "/api/v0/build-action/artefact?"; printer = printRawData; chunkHandler = printChunk; appendAsQueryParam(path, singleBuildActionIdArg, "id"); appendAsQueryParam(path, buildActionFilePathArg, "name"); }); streamArtefactBuildActionArg.setSubArguments({ &singleBuildActionIdArg, &buildActionFilePathArg }); auto createBuildActionArg = OperationArgument("create", '\0', "creates and starts a new build action (or pre-defined task)"); auto taskArg = ConfigValueArgument("task", '\0', "specifies the pre-defined task to run", { "task" }); auto typeArg = ConfigValueArgument("type", '\0', "specifies the action type", { "type" }); auto directoryArg = ConfigValueArgument("directory", '\0', "specifies the directory", { "path" }); auto startConditionArg = ConfigValueArgument("start-condition", '\0', "specifies the start condition", { "immediately/after/manually" }); auto startAfterIdArg = ConfigValueArgument("start-after-id", '\0', "specifies the IDs of existing build actions to start the new action after", { "ID" }); auto sourceRepoArg = ConfigValueArgument("source-repo", '\0', "specifies the source repositories", { "database-name" }); auto destinationRepoArg = ConfigValueArgument("destination-repo", '\0', "specifies the destination repositories", { "database-name" }); auto packagesArg = ConfigValueArgument("package", '\0', "specifies the packages", { "package-name" }); startConditionArg.setPreDefinedCompletionValues("immediately after manually"); startAfterIdArg.setRequiredValueCount(Argument::varValueCount); sourceRepoArg.setRequiredValueCount(Argument::varValueCount); destinationRepoArg.setRequiredValueCount(Argument::varValueCount); packagesArg.setRequiredValueCount(Argument::varValueCount); createBuildActionArg.setCallback([&verb, &path, &printer, &taskArg, &typeArg, &directoryArg, &startConditionArg, &startAfterIdArg, &sourceRepoArg, &destinationRepoArg, &packagesArg](const ArgumentOccurrence &) { verb = boost::beast::http::verb::post; path = "/api/v0/build-action?"; printer = printBuildAction; if (taskArg.isPresent() && typeArg.isPresent()) { throw ParseError("The arguments --task and --type can not be combined."); } appendAsQueryParam(path, { &(taskArg.isPresent() ? taskArg : typeArg), &directoryArg, &startConditionArg, &startAfterIdArg, &sourceRepoArg, &destinationRepoArg, &packagesArg }); }); createBuildActionArg.setSubArguments( { &typeArg, &taskArg, &directoryArg, &startConditionArg, &startAfterIdArg, &sourceRepoArg, &destinationRepoArg, &packagesArg }); auto deleteBuildActionArg = OperationArgument("delete", '\0', "deletes a build action"); deleteBuildActionArg.setCallback([&verb, &path, &printer, &buildActionIdArg](const ArgumentOccurrence &) { verb = boost::beast::http::verb::delete_; path = "/api/v0/build-action?"; printer = printRawData; appendAsQueryParam(path, buildActionIdArg, "id"); }); deleteBuildActionArg.setSubArguments({ &buildActionIdArg }); auto cloneBuildActionArg = OperationArgument("clone", '\0', "clones a build action"); cloneBuildActionArg.setCallback([&verb, &path, &printer, &buildActionIdArg](const ArgumentOccurrence &) { verb = boost::beast::http::verb::post; path = "/api/v0/build-action/clone?"; printer = printRawData; appendAsQueryParam(path, buildActionIdArg, "id"); }); cloneBuildActionArg.setSubArguments({ &buildActionIdArg }); auto startBuildActionArg = OperationArgument("start", '\0', "starts a build action"); startBuildActionArg.setCallback([&verb, &path, &printer, &buildActionIdArg](const ArgumentOccurrence &) { verb = boost::beast::http::verb::post; path = "/api/v0/build-action/start?"; printer = printRawData; appendAsQueryParam(path, buildActionIdArg, "id"); }); startBuildActionArg.setSubArguments({ &buildActionIdArg }); auto stopBuildActionArg = OperationArgument("stop", '\0', "stops a build action"); stopBuildActionArg.setCallback([&verb, &path, &printer, &buildActionIdArg](const ArgumentOccurrence &) { verb = boost::beast::http::verb::post; path = "/api/v0/build-action/stop?"; printer = printRawData; appendAsQueryParam(path, buildActionIdArg, "id"); }); stopBuildActionArg.setSubArguments({ &buildActionIdArg }); actionArg.setSubArguments( { &listActionsArg, &showBuildActionArg, &streamOutputBuildActionArg, &streamLogfileBuildActionArg, &streamArtefactBuildActionArg, &createBuildActionArg, &deleteBuildActionArg, &cloneBuildActionArg, &startBuildActionArg, &stopBuildActionArg }); auto apiArg = OperationArgument("api", '\0', "Invoke a generic API request:"); auto pathArg = ConfigValueArgument("path", '\0', "specifies the route's path without prefix", { "path/of/route?foo=bar&bar=foo" }); pathArg.setImplicit(true); pathArg.setRequired(true); auto methodArg = ConfigValueArgument("method", 'x', "specifies the method", { "GET/POST/PUT/DELETE" }); methodArg.setPreDefinedCompletionValues("GET POST PUT DELETE"); apiArg.setCallback([&verb, &path, &printer, &pathArg, &methodArg](const ArgumentOccurrence &) { const auto *rawVerb = methodArg.firstValueOr("GET"); verb = boost::beast::http::string_to_verb(rawVerb); if (verb == boost::beast::http::verb::unknown) { throw ParseError(argsToString('\"', rawVerb, "\" is not a valid method.")); } path = argsToString("/api/v0/", pathArg.values(0).front()); printer = printRawData; }); apiArg.setSubArguments({ &pathArg, &methodArg }); auto helpArg = HelpArgument(parser); auto noColorArg = NoColorArgument(); parser.setMainArguments({ &packageArg, &actionArg, &apiArg, &instanceArg, &configFileArg, &rawArg, &verboseArg, &noColorArg, &helpArg }); parser.parseArgs(argc, argv); // return early if no operation specified if (!printer) { if (!helpArg.isPresent()) { std::cerr << "No command specified; use --help to list available commands.\n"; } return 0; } // parse config auto config = ClientConfig(); try { config.parse(configFileArg, instanceArg); if (verboseArg.isPresent()) { std::cerr << Phrases::InfoMessage << "Read config from: " << config.path << std::endl; } } catch (const std::runtime_error &e) { std::cerr << Phrases::ErrorMessage << "Unable to parse config: " << e.what() << Phrases::End; std::cerr << Phrases::InfoMessage << "Path of config file was: " << (config.path ? config.path : "[none]") << Phrases::End; return 10; } // make HTTP request and show response const auto url = config.url + path; auto ioContext = boost::asio::io_context(); auto sslContext = boost::asio::ssl::context{ boost::asio::ssl::context::sslv23_client }; auto returnCode = 0; sslContext.set_verify_mode(boost::asio::ssl::verify_peer); sslContext.set_default_verify_paths(); if (verboseArg.isPresent()) { std::cerr << Phrases::InfoMessage << verb << ':' << ' ' << url << std::endl; } LibRepoMgr::WebClient::runSessionFromUrl(ioContext, sslContext, url, std::bind(&handleResponse, std::ref(url), std::placeholders::_1, std::placeholders::_2, rawArg.isPresent() ? printRawData : printer, std::ref(returnCode)), std::string(), config.userName, config.password, verb, std::nullopt, chunkHandler); ioContext.run(); return returnCode; }