From d0135da4fd3a68cbb8d1c634cdf37a2301318ba0 Mon Sep 17 00:00:00 2001 From: Tiago Garcia Date: Fri, 19 Jul 2024 16:51:53 +0100 Subject: [PATCH] Add run-actions Signed-off-by: Tiago Garcia --- README.md | 27 ++++ .../{run-scripts.hpp => run-actions.hpp} | 2 +- src/endpoints/run-actions.cpp | 128 ++++++++++++++++++ src/endpoints/run-scripts.cpp | 11 -- src/routes.cpp | 18 +-- 5 files changed, 165 insertions(+), 21 deletions(-) rename include/endpoints/{run-scripts.hpp => run-actions.hpp} (74%) create mode 100644 src/endpoints/run-actions.cpp delete mode 100644 src/endpoints/run-scripts.cpp diff --git a/README.md b/README.md index 417acf5..d3e6958 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,33 @@ The configuration file must contain the `update-files` field, which is an object } ``` +### `/run-actions` + +#### Webhook event: `push` + +This endpoint allows the application to run specific actions when a push to a specific branch is made. This way, there's no need to manually run the actions on the server. + +The configuration file must contain the `run-actions` field, which is an object with the following format: + +```json +"run-actions": { + "owner/repo-name": { + "branch": "main", + "actions": [ + { + "name": "action-name", + "command": "command-to-run", + "args": [ + "arg1", + "arg2", + "..." + ] + } + ] + } +} +``` + ## License This project is licensed under the GNU General Public License v3.0 - see the [LICENSE](LICENSE) file for details. diff --git a/include/endpoints/run-scripts.hpp b/include/endpoints/run-actions.hpp similarity index 74% rename from include/endpoints/run-scripts.hpp rename to include/endpoints/run-actions.hpp index e247aaf..f74c8d4 100644 --- a/include/endpoints/run-scripts.hpp +++ b/include/endpoints/run-actions.hpp @@ -5,6 +5,6 @@ #include #include -crow::response run_scripts(const nlohmann::json &, const nlohmann::json &,const crow::request &); +crow::response run_actions(const nlohmann::json &, const nlohmann::json &,const crow::request &); #endif diff --git a/src/endpoints/run-actions.cpp b/src/endpoints/run-actions.cpp new file mode 100644 index 0000000..8e716e9 --- /dev/null +++ b/src/endpoints/run-actions.cpp @@ -0,0 +1,128 @@ +#include "endpoints/run-actions.hpp" + +#include "logger.hpp" + +crow::response run_actions(const nlohmann::json &run_actions, const nlohmann::json &tokens, const crow::request &req) { + nlohmann::json payload; + try { + payload = nlohmann::json::parse(req.body); + } catch (nlohmann::json::parse_error& e) { + Logger::error("[/run-actions] Error parsing payload: " + std::string(e.what())); + nlohmann::json response = { + {"status", 400}, + {"error", "Error parsing payload"} + }; + return crow::response(400, response.dump()); + } + + std::string ref; + std::string repo; + + try { + ref = payload["ref"]; + if (size_t last_slash = ref.find_last_of('/'); last_slash != std::string::npos && last_slash + 1 < ref.length()) + ref = ref.substr(last_slash + 1); + repo = payload["repository"]["full_name"]; + } catch (nlohmann::json::out_of_range& e) { + Logger::error("[/run-actions] Invalid JSON payload: " + std::string(e.what())); + nlohmann::json response = { + {"status", 400}, + {"error", "Invalid JSON payload"} + }; + return crow::response(400, response.dump()); + } + + Logger::info("[/run-actions] Received push to " + repo + ":" + ref); + + if (run_actions.find(repo) == run_actions.end()) { + Logger::warn("[/run-actions] No run-actions webhook configuration for repo " + repo); + nlohmann::json response = { + {"status", 404}, + {"error", "No run-actions webhook configuration for repo"} + }; + return crow::response(404, response.dump()); + } + + Logger::info("[/run-actions] Found run-actions webhook configuration for repo " + repo); + + nlohmann::json config; + std::string config_branch; + nlohmann::json config_actions; + try { + config = run_actions[repo]; + config_branch = config["branch"]; + config_actions = config["actions"]; + } catch (nlohmann::json::out_of_range& e) { + Logger::error("[/run-actions] Invalid run-actions configuration for repo " + repo + ": " + std::string(e.what())); + nlohmann::json response = { + {"status", 500}, + {"error", "Invalid run-actions configuration"} + }; + return crow::response(500, response.dump()); + } + + if (ref != config_branch) { + Logger::info("[/run-actions] Ignoring push to " + repo + " on branch " + ref); + nlohmann::json response = { + {"status", 200}, + {"message", "Ignoring push to " + repo + " on branch " + ref} + }; + return crow::response(200, response.dump()); + } + + if (config_actions.empty()) { + Logger::info("[/run-actions] No actions configured for " + repo + " on branch " + ref); + nlohmann::json response = { + {"status", 200}, + {"message", "No actions configured for " + repo + " on branch " + ref} + }; + return crow::response(200, response.dump()); + } + + Logger::info("[/run-actions] Running actions for " + repo + " on branch " + ref); + + nlohmann::json response = { + {"status", 200}, + {"message", "OK"}, + {"successful-actions", nlohmann::json::array()}, + {"failed-actions", nlohmann::json::array()} + }; + for (const auto &action : config_actions) { + std::string action_name; + std::string action_command; + nlohmann::json action_args; + try { + action_name = action["name"]; + action_command = action["command"]; + action_args = action["args"]; + } catch (nlohmann::json::out_of_range& e) { + Logger::error("[/run-actions] Invalid action configuration for repo " + repo + ": " + std::string(e.what())); + nlohmann::json response = { + {"status", 500}, + {"error", "Invalid action configuration"} + }; + return crow::response(500, response.dump()); + } + + Logger::info("[/run-actions] Running action '" + action_name + "'"); + + std::string action_command_with_args = action_command; + for (const auto &arg : action_args) { + action_command_with_args += " " + arg.get(); + } + + int ret = std::system(action_command_with_args.c_str()); + if (ret == 0) { + Logger::info("[/run-actions] Action " + action_name + " completed successfully"); + response["successful-actions"].push_back(action_name); + } else { + Logger::error("[/run-actions] Action " + action_name + " failed with exit code " + std::to_string(ret)); + response["failed-actions"].push_back(action_name); + } + } + + Logger::success("[/run-actions] Finished running actions for " + repo + " on branch " + ref); + + return crow::response(200, response.dump()); +} + diff --git a/src/endpoints/run-scripts.cpp b/src/endpoints/run-scripts.cpp deleted file mode 100644 index 3f6bffb..0000000 --- a/src/endpoints/run-scripts.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "endpoints/run-scripts.hpp" - -crow::response run_scripts(const nlohmann::json &run_scripts, const nlohmann::json &tokens, const crow::request &req) { - // TODO: Implement run_scripts - nlohmann::json response = { - {"status", 501}, - {"error", "Not Implemented"} - }; - return crow::response(501, response.dump()); -} - diff --git a/src/routes.cpp b/src/routes.cpp index c5a434d..b21192f 100644 --- a/src/routes.cpp +++ b/src/routes.cpp @@ -1,6 +1,6 @@ #include "routes.hpp" #include "endpoints/update-files.hpp" -#include "endpoints/run-scripts.hpp" +#include "endpoints/run-actions.hpp" #include #include "logger.hpp" @@ -8,7 +8,7 @@ Routes::Routes(nlohmann::json config) { Logger::info("Partitioning configuration"); const nlohmann::json config_update_files = config["update-files"]; - const nlohmann::json config_run_scripts = config["run-scripts"]; + const nlohmann::json config_run_actions = config["run-actions"]; const nlohmann::json config_tokens = config["tokens"]; Logger::info("Registering route \"/\""); @@ -42,16 +42,16 @@ Routes::Routes(nlohmann::json config) { }); } - if (!config_run_scripts.is_null()) { - Logger::info("Registering route \"/run-scripts\""); - CROW_ROUTE(this->app, "/run-scripts") + if (!config_run_actions.is_null()) { + Logger::info("Registering route \"/run-actions\""); + CROW_ROUTE(this->app, "/run-actions") .methods("POST"_method) - .name("Run Scripts") - ([&config_run_scripts, &config_tokens](const crow::request &req) { + .name("Run Actions") + ([&config_run_actions, &config_tokens](const crow::request &req) { try { - return run_scripts(config_run_scripts, config_tokens, req); + return run_actions(config_run_actions, config_tokens, req); } catch (const std::exception &e) { - Logger::error("Unknown error in run_scripts: " + std::string(e.what())); + Logger::error("Unknown error in run_actions: " + std::string(e.what())); nlohmann::json response = { {"status", 500}, {"error", "Internal server error"}