mirror of
https://github.com/ohwgiles/laminar.git
synced 2024-10-27 20:34:20 +00:00
Added support for metadata for job runs
With the `laminarc tag` command the build scripts can augment runs with metadata. This can be SCM-oriented information like the used branch or source-version. The metadata is a list of key/value pairs. The metadata is automatically shown in the run-view of a job in the web UI. Each call of `laminarc tag` can assign one key/value pair. Example: ``` laminarc tag $JOBNAME $NUMBER` \ Branch "$(git -C $WORKSPACE branch --show-current)" ```
This commit is contained in:
parent
a1a95c8e7f
commit
accdff2405
@ -60,6 +60,7 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
|||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-unused-parameter -Wno-sign-compare")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-unused-parameter -Wno-sign-compare")
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-error=deprecated-declarations" )
|
||||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Werror -DDEBUG")
|
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Werror -DDEBUG")
|
||||||
|
|
||||||
# Allow passing in the version string, for e.g. patched/packaged versions
|
# Allow passing in the version string, for e.g. patched/packaged versions
|
||||||
@ -142,6 +143,7 @@ set(LAMINARD_CORE_SOURCES
|
|||||||
src/run.cpp
|
src/run.cpp
|
||||||
src/server.cpp
|
src/server.cpp
|
||||||
src/version.cpp
|
src/version.cpp
|
||||||
|
src/json.cpp
|
||||||
laminar.capnp.c++
|
laminar.capnp.c++
|
||||||
index_html_size.h
|
index_html_size.h
|
||||||
)
|
)
|
||||||
@ -206,4 +208,4 @@ if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
|
|||||||
install(FILES etc/laminar.conf DESTINATION ${CMAKE_INSTALL_PREFIX}/etc)
|
install(FILES etc/laminar.conf DESTINATION ${CMAKE_INSTALL_PREFIX}/etc)
|
||||||
configure_file(etc/laminar.service.in laminar.service @ONLY)
|
configure_file(etc/laminar.service.in laminar.service @ONLY)
|
||||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/laminar.service DESTINATION ${CMAKE_INSTALL_PREFIX}${SYSTEMD_UNITDIR})
|
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/laminar.service DESTINATION ${CMAKE_INSTALL_PREFIX}${SYSTEMD_UNITDIR})
|
||||||
endif()
|
endif()
|
||||||
|
@ -494,6 +494,22 @@ TIMEOUT=120
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
# Adding metadata to runs
|
||||||
|
|
||||||
|
With the `laminarc tag` command the build scripts can augment runs with metadata.
|
||||||
|
This can be SCM-oriented information like the used branch or release-version.
|
||||||
|
The metadata is a list of key/value pairs.
|
||||||
|
The metadata is automatically shown in the run-view of a job in the web UI.
|
||||||
|
|
||||||
|
Each call of `laminarc tag` can assign one key/value pair.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```
|
||||||
|
laminarc tag $JOBNAME $NUMBER Branch "$(git -C $WORKSPACE branch --show-current)"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
# Contexts
|
# Contexts
|
||||||
|
|
||||||
In Laminar, each run of a job is associated with a context. The context defines an integer number of *executors*, which is the amount of runs which the context will accept simultaneously. A context may also provide additional environment variables.
|
In Laminar, each run of a job is associated with a context. The context defines an integer number of *executors*, which is the amount of runs which the context will accept simultaneously. A context may also provide additional environment variables.
|
||||||
|
@ -90,17 +90,19 @@ static void usage(std::ostream& out) {
|
|||||||
out << "Usage: laminarc [-h|--help] COMMAND\n";
|
out << "Usage: laminarc [-h|--help] COMMAND\n";
|
||||||
out << " -h|--help show this help message\n";
|
out << " -h|--help show this help message\n";
|
||||||
out << "where COMMAND is:\n";
|
out << "where COMMAND is:\n";
|
||||||
out << " queue JOB_LIST... queues one or more jobs for execution and returns immediately.\n";
|
out << " queue JOB_LIST... queues one or more jobs for execution and returns immediately.\n";
|
||||||
out << " start JOB_LIST... queues one or more jobs for execution and blocks until it starts.\n";
|
out << " start JOB_LIST... queues one or more jobs for execution and blocks until it starts.\n";
|
||||||
out << " run JOB_LIST... queues one or more jobs for execution and blocks until it finishes.\n";
|
out << " run JOB_LIST... queues one or more jobs for execution and blocks until it finishes.\n";
|
||||||
out << " JOB_LIST may be prepended with --next, in this case the job will\n";
|
out << " JOB_LIST may be prepended with --next, in this case the job will\n";
|
||||||
out << " be pushed to the front of the queue instead of the end.\n";
|
out << " be pushed to the front of the queue instead of the end.\n";
|
||||||
out << " set PARAMETER_LIST... sets the given parameters as environment variables in the currently\n";
|
out << " set PARAMETER_LIST... sets the given parameters as environment variables in the currently\n";
|
||||||
out << " running job. Fails if run outside of a job context.\n";
|
out << " running job. Fails if run outside of a job context.\n";
|
||||||
out << " abort NAME NUMBER aborts the run identified by NAME and NUMBER.\n";
|
out << " abort NAME NUMBER aborts the run identified by NAME and NUMBER.\n";
|
||||||
out << " show-jobs lists all known jobs.\n";
|
out << " tag NAME NUMBER KEY VALUE sets an key/value pair in the metadata for an run by NAME and NUMBER.\n";
|
||||||
out << " show-queued lists currently queued jobs.\n";
|
out << " This is intended to be used by the build scripts, not the user.\n";
|
||||||
out << " show-running lists currently running jobs.\n";
|
out << " show-jobs lists all known jobs.\n";
|
||||||
|
out << " show-queued lists currently queued jobs.\n";
|
||||||
|
out << " show-running lists currently running jobs.\n";
|
||||||
out << "JOB_LIST is of the form:\n";
|
out << "JOB_LIST is of the form:\n";
|
||||||
out << " [JOB_NAME [PARAMETER_LIST...]]...\n";
|
out << " [JOB_NAME [PARAMETER_LIST...]]...\n";
|
||||||
out << "PARAMETER_LIST is of the form:\n";
|
out << "PARAMETER_LIST is of the form:\n";
|
||||||
@ -244,6 +246,20 @@ int main(int argc, char** argv) {
|
|||||||
for(auto it : running.getResult()) {
|
for(auto it : running.getResult()) {
|
||||||
printf("%s:%d\n", it.getJob().cStr(), it.getBuildNum());
|
printf("%s:%d\n", it.getJob().cStr(), it.getBuildNum());
|
||||||
}
|
}
|
||||||
|
} else if(strcmp(argv[1], "tag") == 0) {
|
||||||
|
if(argc != 6) {
|
||||||
|
fprintf(stderr, "Usage %s tag <jobName> <jobNumber> <metaKey> <metaValue>\n", argv[0]);
|
||||||
|
return EXIT_BAD_ARGUMENT;
|
||||||
|
}
|
||||||
|
auto req = laminar.tagRequest();
|
||||||
|
req.getRun().setJob(argv[2]);
|
||||||
|
req.getRun().setBuildNum(atoi(argv[3]));
|
||||||
|
req.setMetaKey( argv[4] );
|
||||||
|
req.setMetaValue( argv[5] );
|
||||||
|
ts.add(req.send().then([&ret](capnp::Response<LaminarCi::TagResults> resp){
|
||||||
|
if(resp.getResult() != LaminarCi::MethodResult::SUCCESS)
|
||||||
|
ret = EXIT_OPERATION_FAILED;
|
||||||
|
}));
|
||||||
} else {
|
} else {
|
||||||
fprintf(stderr, "Unknown command %s\n", argv[1]);
|
fprintf(stderr, "Unknown command %s\n", argv[1]);
|
||||||
return EXIT_BAD_ARGUMENT;
|
return EXIT_BAD_ARGUMENT;
|
||||||
|
39
src/json.cpp
Normal file
39
src/json.cpp
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
///
|
||||||
|
/// Copyright 2015-2022 Oliver Giles
|
||||||
|
///
|
||||||
|
/// This file is part of Laminar
|
||||||
|
///
|
||||||
|
/// Laminar is free software: you can redistribute it and/or modify
|
||||||
|
/// it under the terms of the GNU General Public License as published by
|
||||||
|
/// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
/// (at your option) any later version.
|
||||||
|
///
|
||||||
|
/// Laminar is distributed in the hope that it will be useful,
|
||||||
|
/// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
/// GNU General Public License for more details.
|
||||||
|
///
|
||||||
|
/// You should have received a copy of the GNU General Public License
|
||||||
|
/// along with Laminar. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
///
|
||||||
|
#include "json.h"
|
||||||
|
|
||||||
|
template<> Json& Json::set(const char* key, double value) { String(key); Double(value); return *this; }
|
||||||
|
template<> Json& Json::set(const char* key, const char* value) { String(key); String(value); return *this; }
|
||||||
|
template<> Json& Json::set(const char* key, std::string value) { String(key); String(value.c_str()); return *this; }
|
||||||
|
|
||||||
|
Json& Json::setJsonObject(const char* key, const std::string& object)
|
||||||
|
{
|
||||||
|
String(key);
|
||||||
|
if( object.length() )
|
||||||
|
{
|
||||||
|
RawValue(object.c_str(), object.length(), rapidjson::kObjectType);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
StartObject();
|
||||||
|
EndObject();
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
47
src/json.h
Normal file
47
src/json.h
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
///
|
||||||
|
/// Copyright 2015-2022 Oliver Giles
|
||||||
|
///
|
||||||
|
/// This file is part of Laminar
|
||||||
|
///
|
||||||
|
/// Laminar is free software: you can redistribute it and/or modify
|
||||||
|
/// it under the terms of the GNU General Public License as published by
|
||||||
|
/// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
/// (at your option) any later version.
|
||||||
|
///
|
||||||
|
/// Laminar is distributed in the hope that it will be useful,
|
||||||
|
/// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
/// GNU General Public License for more details.
|
||||||
|
///
|
||||||
|
/// You should have received a copy of the GNU General Public License
|
||||||
|
/// along with Laminar. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
///
|
||||||
|
#ifndef LAMINAR_JSON_H_
|
||||||
|
#define LAMINAR_JSON_H_
|
||||||
|
|
||||||
|
#include <rapidjson/stringbuffer.h>
|
||||||
|
#include <rapidjson/writer.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
// rapidjson::Writer with a StringBuffer is used a lot in Laminar for
|
||||||
|
// preparing JSON messages to send to HTTP clients. A small wrapper
|
||||||
|
// class here reduces verbosity later for this common use case.
|
||||||
|
class Json : public rapidjson::Writer<rapidjson::StringBuffer> {
|
||||||
|
public:
|
||||||
|
Json() : rapidjson::Writer<rapidjson::StringBuffer>(buf) { StartObject(); }
|
||||||
|
template<typename T>
|
||||||
|
Json& set(const char* key, T value) { String(key); Int64(value); return *this; }
|
||||||
|
Json& startObject(const char* key) { String(key); StartObject(); return *this; }
|
||||||
|
Json& startArray(const char* key) { String(key); StartArray(); return *this; }
|
||||||
|
Json& setJsonObject(const char* key, const std::string& object);
|
||||||
|
const char* str() { EndObject(); return buf.GetString(); }
|
||||||
|
private:
|
||||||
|
rapidjson::StringBuffer buf;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<> Json& Json::set(const char* key, double value);
|
||||||
|
template<> Json& Json::set(const char* key, const char* value);
|
||||||
|
template<> Json& Json::set(const char* key, std::string value);
|
||||||
|
|
||||||
|
|
||||||
|
#endif // ? ! LAMINAR_LAMINAR_H_
|
@ -9,6 +9,7 @@ interface LaminarCi {
|
|||||||
listRunning @4 () -> (result :List(Run));
|
listRunning @4 () -> (result :List(Run));
|
||||||
listKnown @5 () -> (result :List(Text));
|
listKnown @5 () -> (result :List(Text));
|
||||||
abort @6 (run :Run) -> (result :MethodResult);
|
abort @6 (run :Run) -> (result :MethodResult);
|
||||||
|
tag @7 (run :Run, metaKey :Text, metaValue: Text) -> (result :MethodResult);
|
||||||
|
|
||||||
struct Run {
|
struct Run {
|
||||||
job @0 :Text;
|
job @0 :Text;
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "http.h"
|
#include "http.h"
|
||||||
#include "rpc.h"
|
#include "rpc.h"
|
||||||
|
#include "json.h"
|
||||||
|
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
@ -34,32 +35,11 @@
|
|||||||
|
|
||||||
#define COMPRESS_LOG_MIN_SIZE 1024
|
#define COMPRESS_LOG_MIN_SIZE 1024
|
||||||
|
|
||||||
#include <rapidjson/stringbuffer.h>
|
|
||||||
#include <rapidjson/writer.h>
|
|
||||||
|
|
||||||
// FNM_EXTMATCH isn't supported under musl
|
// FNM_EXTMATCH isn't supported under musl
|
||||||
#if !defined(FNM_EXTMATCH)
|
#if !defined(FNM_EXTMATCH)
|
||||||
#define FNM_EXTMATCH 0
|
#define FNM_EXTMATCH 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// rapidjson::Writer with a StringBuffer is used a lot in Laminar for
|
|
||||||
// preparing JSON messages to send to HTTP clients. A small wrapper
|
|
||||||
// class here reduces verbosity later for this common use case.
|
|
||||||
class Json : public rapidjson::Writer<rapidjson::StringBuffer> {
|
|
||||||
public:
|
|
||||||
Json() : rapidjson::Writer<rapidjson::StringBuffer>(buf) { StartObject(); }
|
|
||||||
template<typename T>
|
|
||||||
Json& set(const char* key, T value) { String(key); Int64(value); return *this; }
|
|
||||||
Json& startObject(const char* key) { String(key); StartObject(); return *this; }
|
|
||||||
Json& startArray(const char* key) { String(key); StartArray(); return *this; }
|
|
||||||
const char* str() { EndObject(); return buf.GetString(); }
|
|
||||||
private:
|
|
||||||
rapidjson::StringBuffer buf;
|
|
||||||
};
|
|
||||||
template<> Json& Json::set(const char* key, double value) { String(key); Double(value); return *this; }
|
|
||||||
template<> Json& Json::set(const char* key, const char* value) { String(key); String(value); return *this; }
|
|
||||||
template<> Json& Json::set(const char* key, std::string value) { String(key); String(value.c_str()); return *this; }
|
|
||||||
|
|
||||||
// short syntax helpers for kj::Path
|
// short syntax helpers for kj::Path
|
||||||
template<typename T>
|
template<typename T>
|
||||||
inline kj::Path operator/(const kj::Path& p, const T& ext) {
|
inline kj::Path operator/(const kj::Path& p, const T& ext) {
|
||||||
@ -100,7 +80,7 @@ Laminar::Laminar(Server &server, Settings settings) :
|
|||||||
"name TEXT, number INT UNSIGNED, node TEXT, queuedAt INT, "
|
"name TEXT, number INT UNSIGNED, node TEXT, queuedAt INT, "
|
||||||
"startedAt INT, completedAt INT, result INT, output TEXT, "
|
"startedAt INT, completedAt INT, result INT, output TEXT, "
|
||||||
"outputLen INT, parentJob TEXT, parentBuild INT, reason TEXT, "
|
"outputLen INT, parentJob TEXT, parentBuild INT, reason TEXT, "
|
||||||
"PRIMARY KEY (name, number DESC))";
|
"metadata TEXT, PRIMARY KEY (name, number DESC))";
|
||||||
db->exec(create_table_stmt);
|
db->exec(create_table_stmt);
|
||||||
|
|
||||||
// Migrate from (name, number) primary key to (name, number DESC).
|
// Migrate from (name, number) primary key to (name, number DESC).
|
||||||
@ -123,6 +103,18 @@ Laminar::Laminar(Server &server, Settings settings) :
|
|||||||
db->exec("CREATE INDEX IF NOT EXISTS idx_completion_time ON builds("
|
db->exec("CREATE INDEX IF NOT EXISTS idx_completion_time ON builds("
|
||||||
"completedAt DESC)");
|
"completedAt DESC)");
|
||||||
|
|
||||||
|
// Update 'metadata' if not existing;
|
||||||
|
db->stmt(
|
||||||
|
"SELECT COUNT(*) AS CNT FROM pragma_table_info('builds')"
|
||||||
|
" WHERE name='metadata'")
|
||||||
|
.fetch<int>([&](int has_metadata) {
|
||||||
|
if( !has_metadata )
|
||||||
|
{
|
||||||
|
LLOG(WARNING, "Updating database to contain 'metadata'");
|
||||||
|
db->exec( "ALTER TABLE 'builds' ADD COLUMN 'metadata' TEXT");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// retrieve the last build numbers
|
// retrieve the last build numbers
|
||||||
db->stmt("SELECT name, MAX(number) FROM builds GROUP BY name")
|
db->stmt("SELECT name, MAX(number) FROM builds GROUP BY name")
|
||||||
.fetch<str,uint>([this](str name, uint build){
|
.fetch<str,uint>([this](str name, uint build){
|
||||||
@ -250,17 +242,21 @@ std::string Laminar::getStatus(MonitorScope scope) {
|
|||||||
j.set("time", time(nullptr));
|
j.set("time", time(nullptr));
|
||||||
j.startObject("data");
|
j.startObject("data");
|
||||||
if(scope.type == MonitorScope::RUN) {
|
if(scope.type == MonitorScope::RUN) {
|
||||||
db->stmt("SELECT queuedAt,startedAt,completedAt,result,reason,parentJob,parentBuild,q.lr IS NOT NULL,q.lr FROM builds "
|
db->stmt("SELECT queuedAt,startedAt,completedAt,result,reason,metadata,parentJob,parentBuild,q.lr IS NOT NULL,q.lr FROM builds "
|
||||||
"LEFT JOIN (SELECT name n, MAX(number), completedAt-startedAt lr FROM builds WHERE result IS NOT NULL GROUP BY n) q ON q.n = name "
|
"LEFT JOIN (SELECT name n, MAX(number), completedAt-startedAt lr FROM builds WHERE result IS NOT NULL GROUP BY n) q ON q.n = name "
|
||||||
"WHERE name = ? AND number = ?")
|
"WHERE name = ? AND number = ?")
|
||||||
.bind(scope.job, scope.num)
|
.bind(scope.job, scope.num)
|
||||||
.fetch<time_t, time_t, time_t, int, std::string, std::string, uint, uint, uint>([&](time_t queued, time_t started, time_t completed, int result, std::string reason, std::string parentJob, uint parentBuild, uint lastRuntimeKnown, uint lastRuntime) {
|
.fetch<time_t, time_t, time_t, int, std::string, std::string, std::string, uint, uint, uint>(
|
||||||
|
[&](time_t queued, time_t started, time_t completed, int result,
|
||||||
|
std::string reason, std::string metadata,std::string parentJob,
|
||||||
|
uint parentBuild, uint lastRuntimeKnown, uint lastRuntime) {
|
||||||
j.set("queued", queued);
|
j.set("queued", queued);
|
||||||
j.set("started", started);
|
j.set("started", started);
|
||||||
if(completed)
|
if(completed)
|
||||||
j.set("completed", completed);
|
j.set("completed", completed);
|
||||||
j.set("result", to_string(completed ? RunState(result) : started ? RunState::RUNNING : RunState::QUEUED));
|
j.set("result", to_string(completed ? RunState(result) : started ? RunState::RUNNING : RunState::QUEUED));
|
||||||
j.set("reason", reason);
|
j.set("reason", reason);
|
||||||
|
j.setJsonObject("metadata", metadata);
|
||||||
j.startObject("upstream").set("name", parentJob).set("num", parentBuild).EndObject(2);
|
j.startObject("upstream").set("name", parentJob).set("num", parentBuild).EndObject(2);
|
||||||
if(lastRuntimeKnown)
|
if(lastRuntimeKnown)
|
||||||
j.set("etc", started + lastRuntime);
|
j.set("etc", started + lastRuntime);
|
||||||
@ -287,18 +283,19 @@ std::string Laminar::getStatus(MonitorScope scope) {
|
|||||||
order_by = "(completedAt-startedAt) " + direction + ", number DESC";
|
order_by = "(completedAt-startedAt) " + direction + ", number DESC";
|
||||||
else
|
else
|
||||||
order_by = "number DESC";
|
order_by = "number DESC";
|
||||||
std::string stmt = "SELECT number,startedAt,completedAt,result,reason FROM builds "
|
std::string stmt = "SELECT number,startedAt,completedAt,result,reason,metadata FROM builds "
|
||||||
"WHERE name = ? AND result IS NOT NULL ORDER BY "
|
"WHERE name = ? AND result IS NOT NULL ORDER BY "
|
||||||
+ order_by + " LIMIT ?,?";
|
+ order_by + " LIMIT ?,?";
|
||||||
db->stmt(stmt.c_str())
|
db->stmt(stmt.c_str())
|
||||||
.bind(scope.job, scope.page * runsPerPage, runsPerPage)
|
.bind(scope.job, scope.page * runsPerPage, runsPerPage)
|
||||||
.fetch<uint,time_t,time_t,int,str>([&](uint build,time_t started,time_t completed,int result,str reason){
|
.fetch<uint,time_t,time_t,int,str,str>([&](uint build,time_t started,time_t completed,int result,str reason,str metadata){
|
||||||
j.StartObject();
|
j.StartObject();
|
||||||
j.set("number", build)
|
j.set("number", build)
|
||||||
.set("completed", completed)
|
.set("completed", completed)
|
||||||
.set("started", started)
|
.set("started", started)
|
||||||
.set("result", to_string(RunState(result)))
|
.set("result", to_string(RunState(result)))
|
||||||
.set("reason", reason)
|
.set("reason", reason)
|
||||||
|
.setJsonObject("metadata", metadata)
|
||||||
.EndObject();
|
.EndObject();
|
||||||
});
|
});
|
||||||
j.EndArray();
|
j.EndArray();
|
||||||
@ -323,6 +320,7 @@ std::string Laminar::getStatus(MonitorScope scope) {
|
|||||||
j.set("started", run->startedAt);
|
j.set("started", run->startedAt);
|
||||||
j.set("result", to_string(RunState::RUNNING));
|
j.set("result", to_string(RunState::RUNNING));
|
||||||
j.set("reason", run->reason());
|
j.set("reason", run->reason());
|
||||||
|
j.setJsonObject("metadata", run->getMetaDataJsonString());
|
||||||
j.EndObject();
|
j.EndObject();
|
||||||
}
|
}
|
||||||
j.EndArray();
|
j.EndArray();
|
||||||
@ -333,6 +331,7 @@ std::string Laminar::getStatus(MonitorScope scope) {
|
|||||||
j.set("number", run->build);
|
j.set("number", run->build);
|
||||||
j.set("result", to_string(RunState::QUEUED));
|
j.set("result", to_string(RunState::QUEUED));
|
||||||
j.set("reason", run->reason());
|
j.set("reason", run->reason());
|
||||||
|
j.setJsonObject("metadata", run->getMetaDataJsonString());
|
||||||
j.EndObject();
|
j.EndObject();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -358,9 +357,10 @@ std::string Laminar::getStatus(MonitorScope scope) {
|
|||||||
j.set("description", desc == jobDescriptions.end() ? "" : desc->second);
|
j.set("description", desc == jobDescriptions.end() ? "" : desc->second);
|
||||||
} else if(scope.type == MonitorScope::ALL) {
|
} else if(scope.type == MonitorScope::ALL) {
|
||||||
j.startArray("jobs");
|
j.startArray("jobs");
|
||||||
db->stmt("SELECT name, number, startedAt, completedAt, result, reason "
|
db->stmt("SELECT name, number, startedAt, completedAt, result, reason, metadata "
|
||||||
"FROM builds GROUP BY name HAVING number = MAX(number)")
|
"FROM builds GROUP BY name HAVING number = MAX(number)")
|
||||||
.fetch<str,uint,time_t,time_t,int,str>([&](str name,uint number, time_t started, time_t completed, int result, str reason){
|
.fetch<str,uint,time_t,time_t,int,str,str>([&](str name,uint number,
|
||||||
|
time_t started, time_t completed, int result, str reason, str metadata){
|
||||||
j.StartObject();
|
j.StartObject();
|
||||||
j.set("name", name);
|
j.set("name", name);
|
||||||
j.set("number", number);
|
j.set("number", number);
|
||||||
@ -368,6 +368,7 @@ std::string Laminar::getStatus(MonitorScope scope) {
|
|||||||
j.set("started", started);
|
j.set("started", started);
|
||||||
j.set("completed", completed);
|
j.set("completed", completed);
|
||||||
j.set("reason", reason);
|
j.set("reason", reason);
|
||||||
|
j.setJsonObject("metadata", metadata);
|
||||||
j.EndObject();
|
j.EndObject();
|
||||||
});
|
});
|
||||||
j.EndArray();
|
j.EndArray();
|
||||||
@ -387,8 +388,9 @@ std::string Laminar::getStatus(MonitorScope scope) {
|
|||||||
j.EndObject();
|
j.EndObject();
|
||||||
} else { // Home page
|
} else { // Home page
|
||||||
j.startArray("recent");
|
j.startArray("recent");
|
||||||
db->stmt("SELECT name,number,node,queuedAt,startedAt,completedAt,result,reason FROM builds WHERE completedAt IS NOT NULL ORDER BY completedAt DESC LIMIT 20")
|
db->stmt("SELECT name,number,node,queuedAt,startedAt,completedAt,result,reason,metadata"
|
||||||
.fetch<str,uint,str,time_t,time_t,time_t,int,str>([&](str name,uint build,str context,time_t queued,time_t started,time_t completed,int result,str reason){
|
" FROM builds WHERE completedAt IS NOT NULL ORDER BY completedAt DESC LIMIT 20")
|
||||||
|
.fetch<str,uint,str,time_t,time_t,time_t,int,str,str>([&](str name,uint build,str context,time_t queued,time_t started,time_t completed,int result,str reason,str metadata){
|
||||||
j.StartObject();
|
j.StartObject();
|
||||||
j.set("name", name)
|
j.set("name", name)
|
||||||
.set("number", build)
|
.set("number", build)
|
||||||
@ -398,6 +400,7 @@ std::string Laminar::getStatus(MonitorScope scope) {
|
|||||||
.set("completed", completed)
|
.set("completed", completed)
|
||||||
.set("result", to_string(RunState(result)))
|
.set("result", to_string(RunState(result)))
|
||||||
.set("reason", reason)
|
.set("reason", reason)
|
||||||
|
.setJsonObject("metadata", metadata)
|
||||||
.EndObject();
|
.EndObject();
|
||||||
});
|
});
|
||||||
j.EndArray();
|
j.EndArray();
|
||||||
@ -620,8 +623,9 @@ std::shared_ptr<Run> Laminar::queueJob(std::string name, ParamMap params, bool f
|
|||||||
else
|
else
|
||||||
queuedJobs.push_back(run);
|
queuedJobs.push_back(run);
|
||||||
|
|
||||||
db->stmt("INSERT INTO builds(name,number,queuedAt,parentJob,parentBuild,reason) VALUES(?,?,?,?,?,?)")
|
db->stmt("INSERT INTO builds(name,number,queuedAt,parentJob,parentBuild,reason,metadata) VALUES(?,?,?,?,?,?,?)")
|
||||||
.bind(run->name, run->build, run->queuedAt, run->parentName, run->parentBuild, run->reason())
|
.bind(run->name, run->build, run->queuedAt, run->parentName, run->parentBuild,
|
||||||
|
run->reason(), run->getMetaDataJsonString())
|
||||||
.exec();
|
.exec();
|
||||||
|
|
||||||
// notify clients
|
// notify clients
|
||||||
@ -633,6 +637,7 @@ std::shared_ptr<Run> Laminar::queueJob(std::string name, ParamMap params, bool f
|
|||||||
.set("result", to_string(RunState::QUEUED))
|
.set("result", to_string(RunState::QUEUED))
|
||||||
.set("queueIndex", frontOfQueue ? 0 : (queuedJobs.size() - 1))
|
.set("queueIndex", frontOfQueue ? 0 : (queuedJobs.size() - 1))
|
||||||
.set("reason", run->reason())
|
.set("reason", run->reason())
|
||||||
|
.setJsonObject("metadata", run->getMetaDataJsonString())
|
||||||
.EndObject();
|
.EndObject();
|
||||||
http->notifyEvent(j.str(), name.c_str());
|
http->notifyEvent(j.str(), name.c_str());
|
||||||
|
|
||||||
@ -652,6 +657,18 @@ void Laminar::abortAll() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Laminar::tag(std::string job, uint buildNum, std::string key, std::string value)
|
||||||
|
{
|
||||||
|
if(Run* run = activeRun(job, buildNum))
|
||||||
|
return run->tag(key, value);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LLOG(WARNING, "No active run with ", job, buildNum);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool Laminar::canQueue(const Context& ctx, const Run& run) const {
|
bool Laminar::canQueue(const Context& ctx, const Run& run) const {
|
||||||
if(ctx.busyExecutors >= ctx.numExecutors)
|
if(ctx.busyExecutors >= ctx.numExecutors)
|
||||||
return false;
|
return false;
|
||||||
@ -722,7 +739,8 @@ bool Laminar::tryStartRun(std::shared_ptr<Run> run, int queueIndex) {
|
|||||||
.set("queued", run->queuedAt)
|
.set("queued", run->queuedAt)
|
||||||
.set("started", run->startedAt)
|
.set("started", run->startedAt)
|
||||||
.set("number", run->build)
|
.set("number", run->build)
|
||||||
.set("reason", run->reason());
|
.set("reason", run->reason())
|
||||||
|
.setJsonObject("metadata", run->getMetaDataJsonString());
|
||||||
db->stmt("SELECT completedAt - startedAt FROM builds WHERE name = ? ORDER BY completedAt DESC LIMIT 1")
|
db->stmt("SELECT completedAt - startedAt FROM builds WHERE name = ? ORDER BY completedAt DESC LIMIT 1")
|
||||||
.bind(run->name)
|
.bind(run->name)
|
||||||
.fetch<uint>([&](uint etc){
|
.fetch<uint>([&](uint etc){
|
||||||
@ -768,8 +786,8 @@ void Laminar::handleRunFinished(Run * r) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
db->stmt("UPDATE builds SET completedAt = ?, result = ?, output = ?, outputLen = ? WHERE name = ? AND number = ?")
|
db->stmt("UPDATE builds SET completedAt = ?, result = ?, output = ?, outputLen = ?, metadata = ? WHERE name = ? AND number = ?")
|
||||||
.bind(completedAt, int(r->result), maybeZipped, logsize, r->name, r->build)
|
.bind(completedAt, int(r->result), maybeZipped, logsize, r->getMetaDataJsonString(), r->name, r->build)
|
||||||
.exec();
|
.exec();
|
||||||
|
|
||||||
// notify clients
|
// notify clients
|
||||||
@ -782,7 +800,8 @@ void Laminar::handleRunFinished(Run * r) {
|
|||||||
.set("completed", completedAt)
|
.set("completed", completedAt)
|
||||||
.set("started", r->startedAt)
|
.set("started", r->startedAt)
|
||||||
.set("result", to_string(r->result))
|
.set("result", to_string(r->result))
|
||||||
.set("reason", r->reason());
|
.set("reason", r->reason())
|
||||||
|
.setJsonObject("metadata", r->getMetaDataJsonString());
|
||||||
j.startArray("artifacts");
|
j.startArray("artifacts");
|
||||||
populateArtifacts(j, r->name, r->build);
|
populateArtifacts(j, r->name, r->build);
|
||||||
j.EndArray();
|
j.EndArray();
|
||||||
|
@ -96,6 +96,9 @@ public:
|
|||||||
// Abort all running jobs
|
// Abort all running jobs
|
||||||
void abortAll();
|
void abortAll();
|
||||||
|
|
||||||
|
// Store metadata for an run
|
||||||
|
bool tag(std::string job, uint buildNum, std::string key, std::string value);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool loadConfiguration();
|
bool loadConfiguration();
|
||||||
void loadCustomizations();
|
void loadCustomizations();
|
||||||
|
@ -176,13 +176,17 @@
|
|||||||
<dt v-show="runComplete(job)">Completed</dt><dd v-show="job.completed">{{formatDate(job.completed)}}</dd>
|
<dt v-show="runComplete(job)">Completed</dt><dd v-show="job.completed">{{formatDate(job.completed)}}</dd>
|
||||||
<dt v-show="job.started">Duration</dt><dd v-show="job.started">{{formatDuration(job.started, job.completed)}}</dd>
|
<dt v-show="job.started">Duration</dt><dd v-show="job.started">{{formatDuration(job.started, job.completed)}}</dd>
|
||||||
</dl>
|
</dl>
|
||||||
<dl v-show="job.artifacts.length">
|
<dl>
|
||||||
<dt>Artifacts</dt>
|
<dt v-show="job.artifacts.length">Artifacts</dt>
|
||||||
<dd>
|
<dd v-show="job.artifacts.length">
|
||||||
<ul style="margin-bottom: 0">
|
<ul style="margin-bottom: 0">
|
||||||
<li v-for="art in job.artifacts"><a :href="art.url" target="_self">{{art.filename}}</a> [{{ art.size | iecFileSize }}]</li>
|
<li v-for="art in job.artifacts"><a :href="art.url" target="_self">{{art.filename}}</a> [{{ art.size | iecFileSize }}]</li>
|
||||||
</ul>
|
</ul>
|
||||||
</dd>
|
</dd>
|
||||||
|
<template v-show="Object.keys(job.metadata).length" v-for="(value, key) in job.metadata">
|
||||||
|
<dt>{{key}}</dt>
|
||||||
|
<dd>{{value}}</dd>
|
||||||
|
</template>
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -318,6 +318,7 @@ button:not([disabled]) { cursor: pointer; color: var(--main-fg); }
|
|||||||
#page-run-detail {
|
#page-run-detail {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(400px, auto) 1fr;
|
grid-template-columns: minmax(400px, auto) 1fr;
|
||||||
|
align-items: start;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
}
|
}
|
||||||
@media (max-width: 780px) {
|
@media (max-width: 780px) {
|
||||||
|
13
src/rpc.cpp
13
src/rpc.cpp
@ -143,6 +143,19 @@ public:
|
|||||||
return kj::READY_NOW;
|
return kj::READY_NOW;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kj::Promise<void> tag(TagContext context) override {
|
||||||
|
std::string jobName = context.getParams().getRun().getJob();
|
||||||
|
uint buildNum = context.getParams().getRun().getBuildNum();
|
||||||
|
std::string metaKey = context.getParams().getMetaKey();
|
||||||
|
std::string metaValue = context.getParams().getMetaValue();
|
||||||
|
LLOG(INFO, "RPC tag", jobName, buildNum, metaKey, metaValue);
|
||||||
|
LaminarCi::MethodResult result = laminar.tag(jobName, buildNum, metaKey, metaValue)
|
||||||
|
? LaminarCi::MethodResult::SUCCESS
|
||||||
|
: LaminarCi::MethodResult::FAILED;
|
||||||
|
context.getResults().setResult(result);
|
||||||
|
return kj::READY_NOW;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Helper to convert an RPC parameter list to a hash map
|
// Helper to convert an RPC parameter list to a hash map
|
||||||
ParamMap params(const capnp::List<LaminarCi::JobParam>::Reader& paramReader) {
|
ParamMap params(const capnp::List<LaminarCi::JobParam>::Reader& paramReader) {
|
||||||
|
17
src/run.cpp
17
src/run.cpp
@ -208,3 +208,20 @@ bool Run::abort() {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Run::tag(const std::string& key, const std::string& value)
|
||||||
|
{
|
||||||
|
LLOG(INFO, "Setting tag ", key, value );
|
||||||
|
metaDataMap[key]=value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Run::getMetaDataJsonString()
|
||||||
|
{
|
||||||
|
Json j;
|
||||||
|
for (const auto& [key, value] : metaDataMap)
|
||||||
|
{
|
||||||
|
j.set(key.c_str(), value);
|
||||||
|
}
|
||||||
|
return( std::string(j.str()) );
|
||||||
|
}
|
||||||
|
10
src/run.h
10
src/run.h
@ -19,7 +19,10 @@
|
|||||||
#ifndef LAMINAR_RUN_H_
|
#ifndef LAMINAR_RUN_H_
|
||||||
#define LAMINAR_RUN_H_
|
#define LAMINAR_RUN_H_
|
||||||
|
|
||||||
|
#include "json.h"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <map>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <list>
|
#include <list>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
@ -62,6 +65,11 @@ public:
|
|||||||
// aborts this run
|
// aborts this run
|
||||||
bool abort();
|
bool abort();
|
||||||
|
|
||||||
|
// store metadata into run
|
||||||
|
bool tag(const std::string& key, const std::string& value);
|
||||||
|
// Return the meta as JSON text
|
||||||
|
std::string getMetaDataJsonString();
|
||||||
|
|
||||||
std::string reason() const;
|
std::string reason() const;
|
||||||
|
|
||||||
kj::Promise<void> whenStarted() { return startedFork.addBranch(); }
|
kj::Promise<void> whenStarted() { return startedFork.addBranch(); }
|
||||||
@ -101,6 +109,8 @@ private:
|
|||||||
kj::ForkedPromise<void> startedFork;
|
kj::ForkedPromise<void> startedFork;
|
||||||
kj::PromiseFulfillerPair<RunState> finished;
|
kj::PromiseFulfillerPair<RunState> finished;
|
||||||
kj::ForkedPromise<RunState> finishedFork;
|
kj::ForkedPromise<RunState> finishedFork;
|
||||||
|
|
||||||
|
std::map<std::string, std::string> metaDataMap;
|
||||||
};
|
};
|
||||||
|
|
||||||
// All this below is a somewhat overengineered method of keeping track of
|
// All this below is a somewhat overengineered method of keeping track of
|
||||||
|
Loading…
Reference in New Issue
Block a user