From a729a6782ef94730932885a1116525c666a0464b Mon Sep 17 00:00:00 2001 From: Oliver Giles Date: Sat, 19 Sep 2015 17:24:20 +0200 Subject: [PATCH] implement display and serving of archived artifacts --- laminar.conf | 10 +++++++++ src/interface.h | 5 +++++ src/laminar.cpp | 43 +++++++++++++++++++++++++++++++++++++- src/laminar.h | 2 ++ src/resources/tpl/run.html | 1 + src/server.cpp | 12 ++++++++++- 6 files changed, 71 insertions(+), 2 deletions(-) diff --git a/laminar.conf b/laminar.conf index 8093558..fdf1254 100644 --- a/laminar.conf +++ b/laminar.conf @@ -15,3 +15,13 @@ ### will not be deleted after the run has completed ### #LAMINAR_KEEP_WORKDIR=1 + +### +### LAMINAR_ARCHIVE_URL +### +### Base url used to request artifacts. Laminar can serve build +### artifacts (and it will if you leave this unset), but it +### uses a very naive and inefficient method. Best to let a real +### webserver handle serving those requests. +### +#LAMINAR_ARCHIVE_URL=http://backbone.example.com/ci/archive diff --git a/src/interface.h b/src/interface.h index 584686f..07d8572 100644 --- a/src/interface.h +++ b/src/interface.h @@ -107,6 +107,11 @@ struct LaminarInterface { // arbitrary parameters on a run (usually itself) to be available in // the environment of subsequent scripts. virtual bool setParam(std::string job, int buildNum, std::string param, std::string value) = 0; + + // Fetches the content of an artifact given its filename relative to + // $LAMINAR_HOME/archive. This shouldn't be used, because the sysadmin + // should have configured a real webserver to serve these things. + virtual bool getArtefact(std::string path, std::string& result) = 0; }; #endif // INTERFACE_H diff --git a/src/laminar.cpp b/src/laminar.cpp index ef3278f..1efafd7 100644 --- a/src/laminar.cpp +++ b/src/laminar.cpp @@ -21,6 +21,7 @@ #include "conf.h" #include +#include #include #include @@ -56,12 +57,15 @@ namespace { // Default values when none were supplied in $LAMINAR_CONF_FILE (/etc/laminar.conf) constexpr const char* INTADDR_RPC_DEFAULT = "unix:\0laminar"; constexpr const char* INTADDR_HTTP_DEFAULT = "*:8080"; -constexpr const char* BASE_CFG_DIR = "/home/og/dev/laminar/cfg"; +constexpr const char* ARCHIVE_URL_DEFAULT = "/archive"; } typedef std::string str; Laminar::Laminar() { + archiveUrl = ARCHIVE_URL_DEFAULT; + if(char* envArchive = getenv("LAMINAR_ARCHIVE_URL")) + archiveUrl = envArchive; eraseWorkdir = true; homeDir = getenv("LAMINAR_HOME") ?: "/var/lib/laminar"; @@ -125,6 +129,20 @@ void Laminar::sendStatus(LaminarClient* client) { j.set("result", to_string(RunState(result))); j.set("reason", reason); }); + j.startArray("artifacts"); + fs::path dir(fs::path(homeDir)/"archive"/client->scope.job/std::to_string(client->scope.num)); + fs::recursive_directory_iterator rdt(dir); + int prefixLen = (fs::path(homeDir)/"archive").string().length(); + int scopeLen = dir.string().length(); + for(fs::directory_entry e : rdt) { + if(!fs::is_regular_file(e)) + continue; + j.StartObject(); + j.set("url", archiveUrl + e.path().string().substr(prefixLen)); + j.set("filename", e.path().string().substr(scopeLen+1)); + j.EndObject(); + } + j.EndArray(); j.EndObject(); client->sendMessage(j.str()); } else if(client->scope.type == MonitorScope::JOB) { @@ -556,3 +574,26 @@ void Laminar::runFinished(const Run * r) { // will delete the job activeJobs.get<2>().erase(r); } + +bool Laminar::getArtefact(std::string path, std::string& result) { + if(archiveUrl != ARCHIVE_URL_DEFAULT) { + // we shouldn't have got here. Probably an invalid link. + return false; + } + // Reads in the whole file into the given string reference. + // This is a terrible way to serve files (especially large ones). + fs::path file(fs::path(homeDir)/"archive"/path); + // no support for browsing archived directories + if(fs::is_directory(file)) + return false; + std::ifstream fstr(file.string()); + fstr.seekg(0, std::ios::end); + size_t sz = fstr.tellg(); + if(fstr.rdstate() == 0) { + result.resize(sz); + fstr.seekg(0); + fstr.read(&result[0], sz); + return true; + } + return false; +} diff --git a/src/laminar.h b/src/laminar.h index 5693e4d..1af5fed 100644 --- a/src/laminar.h +++ b/src/laminar.h @@ -53,6 +53,7 @@ public: void deregisterClient(LaminarClient* client) override; void sendStatus(LaminarClient* client) override; bool setParam(std::string job, int buildNum, std::string param, std::string value) override; + bool getArtefact(std::string path, std::string& result) override; private: bool loadConfiguration(); @@ -85,6 +86,7 @@ private: std::string homeDir; std::set clients; bool eraseWorkdir; + std::string archiveUrl; }; #endif // _LAMINAR_LAMINAR_H_ diff --git a/src/resources/tpl/run.html b/src/resources/tpl/run.html index f876f04..405816d 100644 --- a/src/resources/tpl/run.html +++ b/src/resources/tpl/run.html @@ -7,6 +7,7 @@
 
Reason
{{job.reason}}
Started
{{job.when}}
+
Artifacts
diff --git a/src/server.cpp b/src/server.cpp index 7fe5f76..644d089 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -168,7 +168,17 @@ public: wss.set_http_handler([this](websocketpp::connection_hdl hdl){ websocket::connection_ptr c = wss.get_con_from_hdl(hdl); const char* start, *end; - if(resources.handleRequest(c->get_resource(), &start, &end)) { + std::string resource = c->get_resource(); + if(resource.compare(0, strlen("/archive/"), "/archive/") == 0) { + std::string file(resource.substr(strlen("/archive/"))); + std::string content; + if(laminar.getArtefact(file, content)) { + c->set_status(websocketpp::http::status_code::ok); + c->set_body(content); + } else { + c->set_status(websocketpp::http::status_code::not_found); + } + } else if(resources.handleRequest(resource, &start, &end)) { c->set_status(websocketpp::http::status_code::ok); c->append_header("Content-Encoding", "gzip"); c->set_body(std::string(start, end));