Compare commits
3 Commits
52ea711718
...
5c8216edd1
Author | SHA1 | Date |
---|---|---|
Tiago Garcia | 5c8216edd1 | |
Tiago Garcia | cd769fa8bc | |
Tiago Garcia | d0135da4fd |
59
README.md
59
README.md
|
@ -137,6 +137,65 @@ 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",
|
||||||
|
"..."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: if you don't want to use the `args` field, just leave an empty array such as `"args": []`.
|
||||||
|
|
||||||
|
## Nginx Configuration
|
||||||
|
|
||||||
|
If you want to use Nginx as a reverse proxy for the application, you can use the following example configuration:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name services.example.com;
|
||||||
|
|
||||||
|
location /gh-wh-handler {
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
add_header Front-End-Https on;
|
||||||
|
|
||||||
|
proxy_headers_hash_max_size 512;
|
||||||
|
proxy_headers_hash_bucket_size 64;
|
||||||
|
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_redirect off;
|
||||||
|
proxy_max_temp_file_size 0;
|
||||||
|
rewrite /gh-wh-handler/(.*) /$1 break;
|
||||||
|
proxy_pass http://127.0.0.1:65001;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This way, you will be able to access the application using the URL `http://services.example.com/gh-wh-handler/end-point`.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is licensed under the GNU General Public License v3.0 - see the [LICENSE](LICENSE) file for details.
|
This project is licensed under the GNU General Public License v3.0 - see the [LICENSE](LICENSE) file for details.
|
||||||
|
|
|
@ -5,6 +5,6 @@
|
||||||
#include <crow/http_response.h>
|
#include <crow/http_response.h>
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
crow::response run_scripts(const nlohmann::json &, const nlohmann::json &,const crow::request &);
|
crow::response run_actions(const nlohmann::json &, const crow::request &);
|
||||||
|
|
||||||
#endif
|
#endif
|
|
@ -9,7 +9,7 @@ void Config::create_config(std::string config_file_path) {
|
||||||
std::cout << "Creating config file" << std::endl;
|
std::cout << "Creating config file" << std::endl;
|
||||||
nlohmann::json config = {
|
nlohmann::json config = {
|
||||||
{"port", 65001},
|
{"port", 65001},
|
||||||
{"tokens", nlohmann::json::array()},
|
{"tokens", {}},
|
||||||
};
|
};
|
||||||
std::string path_to_config = config_file_path.substr(0, config_file_path.find_last_of('/'));
|
std::string path_to_config = config_file_path.substr(0, config_file_path.find_last_of('/'));
|
||||||
if (!std::filesystem::exists(path_to_config)) {
|
if (!std::filesystem::exists(path_to_config)) {
|
||||||
|
|
|
@ -0,0 +1,128 @@
|
||||||
|
#include "endpoints/run-actions.hpp"
|
||||||
|
|
||||||
|
#include "logger.hpp"
|
||||||
|
|
||||||
|
crow::response run_actions(const nlohmann::json &run_actions, 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<std::string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
|
@ -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());
|
|
||||||
}
|
|
||||||
|
|
|
@ -117,10 +117,14 @@ void Logger::log(std::string message, std::string level) {
|
||||||
formatted_message += "(" + std::string(time_buffer) + ") ";
|
formatted_message += "(" + std::string(time_buffer) + ") ";
|
||||||
|
|
||||||
if (level == "CODE") {
|
if (level == "CODE") {
|
||||||
|
#if defined(__aarch64__)
|
||||||
|
formatted_message += "\n" + message + "\n";
|
||||||
|
#else
|
||||||
struct winsize w;
|
struct winsize w;
|
||||||
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
|
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
|
||||||
int term_width = w.ws_col > 250 ? 250 : w.ws_col - 1;
|
int term_width = w.ws_col > 250 ? 250 : w.ws_col - 1;
|
||||||
formatted_message += "\n" + std::string(term_width, '=') + "\n" + message + "\n" + std::string(term_width, '=');
|
formatted_message += "\n" + std::string(term_width, '=') + "\n" + message + "\n" + std::string(term_width, '=');
|
||||||
|
#endif
|
||||||
} else {
|
} else {
|
||||||
formatted_message += "[" + level + "] " + message;
|
formatted_message += "[" + level + "] " + message;
|
||||||
}
|
}
|
||||||
|
@ -129,10 +133,14 @@ void Logger::log(std::string message, std::string level) {
|
||||||
}
|
}
|
||||||
std::cout << formatted_message << std::endl;
|
std::cout << formatted_message << std::endl;
|
||||||
if (level == "CODE") {
|
if (level == "CODE") {
|
||||||
|
#if defined(__aarch64__)
|
||||||
|
Logger::log_file << std::endl << message << std::endl;
|
||||||
|
#else
|
||||||
struct winsize w;
|
struct winsize w;
|
||||||
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
|
ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
|
||||||
int term_width = w.ws_col > 250 ? 250 : w.ws_col - 1;
|
int term_width = w.ws_col > 250 ? 250 : w.ws_col - 1;
|
||||||
Logger::log_file << std::string(term_width, '=') << std::endl << message << std::endl << std::string(term_width, '=') << std::endl;
|
Logger::log_file << std::string(term_width, '=') << std::endl << message << std::endl << std::string(term_width, '=') << std::endl;
|
||||||
|
#endif
|
||||||
} else {
|
} else {
|
||||||
Logger::log_file << "[" << level << "] " << message << std::endl;
|
Logger::log_file << "[" << level << "] " << message << std::endl;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
#include "routes.hpp"
|
#include "routes.hpp"
|
||||||
#include "endpoints/update-files.hpp"
|
#include "endpoints/update-files.hpp"
|
||||||
#include "endpoints/run-scripts.hpp"
|
#include "endpoints/run-actions.hpp"
|
||||||
#include <crow/app.h>
|
#include <crow/app.h>
|
||||||
|
|
||||||
#include "logger.hpp"
|
#include "logger.hpp"
|
||||||
|
|
||||||
Routes::Routes(nlohmann::json config) {
|
Routes::Routes(nlohmann::json config) {
|
||||||
Logger::info("Partitioning configuration");
|
Logger::info("[Routes] Partitioning configuration");
|
||||||
const nlohmann::json config_update_files = config["update-files"];
|
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"];
|
const nlohmann::json config_tokens = config["tokens"];
|
||||||
|
|
||||||
Logger::info("Registering route \"/\"");
|
Logger::info("[Routes] Registering route \"/\"");
|
||||||
CROW_ROUTE(this->app, "/")
|
CROW_ROUTE(this->app, "/")
|
||||||
.methods("POST"_method)
|
.methods("POST"_method, "GET"_method)
|
||||||
.name("Ping")
|
.name("Ping")
|
||||||
([]() {
|
([]() {
|
||||||
nlohmann::json response = {
|
nlohmann::json response = {
|
||||||
|
@ -23,16 +23,23 @@ Routes::Routes(nlohmann::json config) {
|
||||||
return crow::response(200, response.dump());
|
return crow::response(200, response.dump());
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!config_update_files.is_null()) {
|
Logger::info("[Routes] Registering route \"/update-files\"");
|
||||||
Logger::info("Registering route \"/update-files\"");
|
|
||||||
CROW_ROUTE(this->app, "/update-files")
|
CROW_ROUTE(this->app, "/update-files")
|
||||||
.methods("POST"_method)
|
.methods("POST"_method)
|
||||||
.name("Update Files")
|
.name("Update Files")
|
||||||
([&config_update_files, &config_tokens](const crow::request &req) {
|
([&config_update_files, &config_tokens](const crow::request &req) {
|
||||||
|
if (config_update_files.is_null()) {
|
||||||
|
Logger::warn("[Routes] No update-files configuration found");
|
||||||
|
nlohmann::json response = {
|
||||||
|
{"status", 404},
|
||||||
|
{"error", "No update-files configuration found"}
|
||||||
|
};
|
||||||
|
return crow::response(404, response.dump());
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
return update_files(config_update_files, config_tokens, req);
|
return update_files(config_update_files, config_tokens, req);
|
||||||
} catch (const std::exception &e) {
|
} catch (const std::exception &e) {
|
||||||
Logger::error("Unknown error in update_files: " + std::string(e.what()));
|
Logger::error("[Routes] Unknown error in update_files: " + std::string(e.what()));
|
||||||
nlohmann::json response = {
|
nlohmann::json response = {
|
||||||
{"status", 500},
|
{"status", 500},
|
||||||
{"error", "Internal server error"}
|
{"error", "Internal server error"}
|
||||||
|
@ -40,18 +47,24 @@ Routes::Routes(nlohmann::json config) {
|
||||||
return crow::response(500, response.dump());
|
return crow::response(500, response.dump());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
if (!config_run_scripts.is_null()) {
|
Logger::info("[Routes] Registering route \"/run-actions\"");
|
||||||
Logger::info("Registering route \"/run-scripts\"");
|
CROW_ROUTE(this->app, "/run-actions")
|
||||||
CROW_ROUTE(this->app, "/run-scripts")
|
|
||||||
.methods("POST"_method)
|
.methods("POST"_method)
|
||||||
.name("Run Scripts")
|
.name("Run Actions")
|
||||||
([&config_run_scripts, &config_tokens](const crow::request &req) {
|
([&config_run_actions](const crow::request &req) {
|
||||||
|
if (config_run_actions.is_null()) {
|
||||||
|
Logger::warn("[Routes] No run-actions configuration found");
|
||||||
|
nlohmann::json response = {
|
||||||
|
{"status", 404},
|
||||||
|
{"error", "No run-actions configuration found"}
|
||||||
|
};
|
||||||
|
return crow::response(404, response.dump());
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
return run_scripts(config_run_scripts, config_tokens, req);
|
return run_actions(config_run_actions, req);
|
||||||
} catch (const std::exception &e) {
|
} catch (const std::exception &e) {
|
||||||
Logger::error("Unknown error in run_scripts: " + std::string(e.what()));
|
Logger::error("[Routes] Unknown error in run_actions: " + std::string(e.what()));
|
||||||
nlohmann::json response = {
|
nlohmann::json response = {
|
||||||
{"status", 500},
|
{"status", 500},
|
||||||
{"error", "Internal server error"}
|
{"error", "Internal server error"}
|
||||||
|
@ -59,13 +72,23 @@ Routes::Routes(nlohmann::json config) {
|
||||||
return crow::response(500, response.dump());
|
return crow::response(500, response.dump());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
Logger::info("Starting server");
|
Logger::info("[Routes] Registering catch-all route (404)");
|
||||||
|
CROW_CATCHALL_ROUTE(this->app)
|
||||||
|
([](const crow::request &req) {
|
||||||
|
nlohmann::json response = {
|
||||||
|
{"status", 404},
|
||||||
|
{"error", "Not found"}
|
||||||
|
};
|
||||||
|
return crow::response(404, response.dump());
|
||||||
|
});
|
||||||
|
|
||||||
|
Logger::info("[Routes] Routes registered");
|
||||||
|
Logger::info("[Routes] Starting server");
|
||||||
try {
|
try {
|
||||||
this->app.port(config["port"].get<int>()).multithreaded().run();
|
this->app.port(config["port"].get<int>()).multithreaded().run();
|
||||||
} catch (const std::exception &e) {
|
} catch (const std::exception &e) {
|
||||||
Logger::fatal("Error starting server: " + std::string(e.what()));
|
Logger::fatal("[Routes] Error starting server: " + std::string(e.what()));
|
||||||
}
|
}
|
||||||
Logger::info("Server stopped");
|
Logger::info("[Routes] Server stopped");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue