From accdff2405a2810e3a8e561d408d3e700d05216d Mon Sep 17 00:00:00 2001 From: Maximilian Seesslen Date: Wed, 27 Mar 2024 22:18:52 +0100 Subject: [PATCH] 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)" ``` --- CMakeLists.txt | 4 +- UserManual.md | 16 +++++++ src/client.cpp | 38 ++++++++++++----- src/json.cpp | 39 +++++++++++++++++ src/json.h | 47 +++++++++++++++++++++ src/laminar.capnp | 1 + src/laminar.cpp | 91 ++++++++++++++++++++++++---------------- src/laminar.h | 3 ++ src/resources/index.html | 10 +++-- src/resources/style.css | 1 + src/rpc.cpp | 13 ++++++ src/run.cpp | 17 ++++++++ src/run.h | 10 +++++ 13 files changed, 239 insertions(+), 51 deletions(-) create mode 100644 src/json.cpp create mode 100644 src/json.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 594b71b..48ac21f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,6 +60,7 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) 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} -Wno-error=deprecated-declarations" ) set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Werror -DDEBUG") # Allow passing in the version string, for e.g. patched/packaged versions @@ -142,6 +143,7 @@ set(LAMINARD_CORE_SOURCES src/run.cpp src/server.cpp src/version.cpp + src/json.cpp laminar.capnp.c++ 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) configure_file(etc/laminar.service.in laminar.service @ONLY) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/laminar.service DESTINATION ${CMAKE_INSTALL_PREFIX}${SYSTEMD_UNITDIR}) -endif() \ No newline at end of file +endif() diff --git a/UserManual.md b/UserManual.md index 0228f46..be86a6c 100644 --- a/UserManual.md +++ b/UserManual.md @@ -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 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. diff --git a/src/client.cpp b/src/client.cpp index 26b4a0f..a61663d 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -90,17 +90,19 @@ static void usage(std::ostream& out) { out << "Usage: laminarc [-h|--help] COMMAND\n"; out << " -h|--help show this help message\n"; out << "where COMMAND is:\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 << " 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 << " 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 << " 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 << " show-jobs lists all known jobs.\n"; - out << " show-queued lists currently queued jobs.\n"; - out << " show-running lists currently running jobs.\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 << " 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 << " 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 << " 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 << " tag NAME NUMBER KEY VALUE sets an key/value pair in the metadata for an run by NAME and NUMBER.\n"; + out << " This is intended to be used by the build scripts, not the user.\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_NAME [PARAMETER_LIST...]]...\n"; out << "PARAMETER_LIST is of the form:\n"; @@ -244,6 +246,20 @@ int main(int argc, char** argv) { for(auto it : running.getResult()) { printf("%s:%d\n", it.getJob().cStr(), it.getBuildNum()); } + } else if(strcmp(argv[1], "tag") == 0) { + if(argc != 6) { + fprintf(stderr, "Usage %s tag \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 resp){ + if(resp.getResult() != LaminarCi::MethodResult::SUCCESS) + ret = EXIT_OPERATION_FAILED; + })); } else { fprintf(stderr, "Unknown command %s\n", argv[1]); return EXIT_BAD_ARGUMENT; diff --git a/src/json.cpp b/src/json.cpp new file mode 100644 index 0000000..bdf00a6 --- /dev/null +++ b/src/json.cpp @@ -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 +/// +#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; +} + diff --git a/src/json.h b/src/json.h new file mode 100644 index 0000000..a82619c --- /dev/null +++ b/src/json.h @@ -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 +/// +#ifndef LAMINAR_JSON_H_ +#define LAMINAR_JSON_H_ + +#include +#include +#include + +// 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 { +public: + Json() : rapidjson::Writer(buf) { StartObject(); } + template + 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_ diff --git a/src/laminar.capnp b/src/laminar.capnp index d68c884..248e910 100644 --- a/src/laminar.capnp +++ b/src/laminar.capnp @@ -9,6 +9,7 @@ interface LaminarCi { listRunning @4 () -> (result :List(Run)); listKnown @5 () -> (result :List(Text)); abort @6 (run :Run) -> (result :MethodResult); + tag @7 (run :Run, metaKey :Text, metaValue: Text) -> (result :MethodResult); struct Run { job @0 :Text; diff --git a/src/laminar.cpp b/src/laminar.cpp index 6b5b281..437e78d 100644 --- a/src/laminar.cpp +++ b/src/laminar.cpp @@ -22,6 +22,7 @@ #include "log.h" #include "http.h" #include "rpc.h" +#include "json.h" #include #include @@ -34,32 +35,11 @@ #define COMPRESS_LOG_MIN_SIZE 1024 -#include -#include - // FNM_EXTMATCH isn't supported under musl #if !defined(FNM_EXTMATCH) #define FNM_EXTMATCH 0 #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 { -public: - Json() : rapidjson::Writer(buf) { StartObject(); } - template - 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 template 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, " "startedAt INT, completedAt INT, result INT, output 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); // 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(" "completedAt DESC)"); + // Update 'metadata' if not existing; + db->stmt( + "SELECT COUNT(*) AS CNT FROM pragma_table_info('builds')" + " WHERE name='metadata'") + .fetch([&](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 db->stmt("SELECT name, MAX(number) FROM builds GROUP BY name") .fetch([this](str name, uint build){ @@ -250,17 +242,21 @@ std::string Laminar::getStatus(MonitorScope scope) { j.set("time", time(nullptr)); j.startObject("data"); 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 " "WHERE name = ? AND number = ?") .bind(scope.job, scope.num) - .fetch([&](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 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("started", started); if(completed) j.set("completed", completed); j.set("result", to_string(completed ? RunState(result) : started ? RunState::RUNNING : RunState::QUEUED)); j.set("reason", reason); + j.setJsonObject("metadata", metadata); j.startObject("upstream").set("name", parentJob).set("num", parentBuild).EndObject(2); if(lastRuntimeKnown) j.set("etc", started + lastRuntime); @@ -287,18 +283,19 @@ std::string Laminar::getStatus(MonitorScope scope) { order_by = "(completedAt-startedAt) " + direction + ", number DESC"; else 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 " + order_by + " LIMIT ?,?"; db->stmt(stmt.c_str()) .bind(scope.job, scope.page * runsPerPage, runsPerPage) - .fetch([&](uint build,time_t started,time_t completed,int result,str reason){ + .fetch([&](uint build,time_t started,time_t completed,int result,str reason,str metadata){ j.StartObject(); j.set("number", build) .set("completed", completed) .set("started", started) .set("result", to_string(RunState(result))) .set("reason", reason) + .setJsonObject("metadata", metadata) .EndObject(); }); j.EndArray(); @@ -323,6 +320,7 @@ std::string Laminar::getStatus(MonitorScope scope) { j.set("started", run->startedAt); j.set("result", to_string(RunState::RUNNING)); j.set("reason", run->reason()); + j.setJsonObject("metadata", run->getMetaDataJsonString()); j.EndObject(); } j.EndArray(); @@ -333,6 +331,7 @@ std::string Laminar::getStatus(MonitorScope scope) { j.set("number", run->build); j.set("result", to_string(RunState::QUEUED)); j.set("reason", run->reason()); + j.setJsonObject("metadata", run->getMetaDataJsonString()); j.EndObject(); } } @@ -358,9 +357,10 @@ std::string Laminar::getStatus(MonitorScope scope) { j.set("description", desc == jobDescriptions.end() ? "" : desc->second); } else if(scope.type == MonitorScope::ALL) { 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)") - .fetch([&](str name,uint number, time_t started, time_t completed, int result, str reason){ + .fetch([&](str name,uint number, + time_t started, time_t completed, int result, str reason, str metadata){ j.StartObject(); j.set("name", name); j.set("number", number); @@ -368,6 +368,7 @@ std::string Laminar::getStatus(MonitorScope scope) { j.set("started", started); j.set("completed", completed); j.set("reason", reason); + j.setJsonObject("metadata", metadata); j.EndObject(); }); j.EndArray(); @@ -387,8 +388,9 @@ std::string Laminar::getStatus(MonitorScope scope) { j.EndObject(); } else { // Home page 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") - .fetch([&](str name,uint build,str context,time_t queued,time_t started,time_t completed,int result,str reason){ + db->stmt("SELECT name,number,node,queuedAt,startedAt,completedAt,result,reason,metadata" + " FROM builds WHERE completedAt IS NOT NULL ORDER BY completedAt DESC LIMIT 20") + .fetch([&](str name,uint build,str context,time_t queued,time_t started,time_t completed,int result,str reason,str metadata){ j.StartObject(); j.set("name", name) .set("number", build) @@ -398,6 +400,7 @@ std::string Laminar::getStatus(MonitorScope scope) { .set("completed", completed) .set("result", to_string(RunState(result))) .set("reason", reason) + .setJsonObject("metadata", metadata) .EndObject(); }); j.EndArray(); @@ -620,8 +623,9 @@ std::shared_ptr Laminar::queueJob(std::string name, ParamMap params, bool f else queuedJobs.push_back(run); - db->stmt("INSERT INTO builds(name,number,queuedAt,parentJob,parentBuild,reason) VALUES(?,?,?,?,?,?)") - .bind(run->name, run->build, run->queuedAt, run->parentName, run->parentBuild, run->reason()) + 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(), run->getMetaDataJsonString()) .exec(); // notify clients @@ -633,6 +637,7 @@ std::shared_ptr Laminar::queueJob(std::string name, ParamMap params, bool f .set("result", to_string(RunState::QUEUED)) .set("queueIndex", frontOfQueue ? 0 : (queuedJobs.size() - 1)) .set("reason", run->reason()) + .setJsonObject("metadata", run->getMetaDataJsonString()) .EndObject(); 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 { if(ctx.busyExecutors >= ctx.numExecutors) return false; @@ -722,7 +739,8 @@ bool Laminar::tryStartRun(std::shared_ptr run, int queueIndex) { .set("queued", run->queuedAt) .set("started", run->startedAt) .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") .bind(run->name) .fetch([&](uint etc){ @@ -768,8 +786,8 @@ void Laminar::handleRunFinished(Run * r) { } } - db->stmt("UPDATE builds SET completedAt = ?, result = ?, output = ?, outputLen = ? WHERE name = ? AND number = ?") - .bind(completedAt, int(r->result), maybeZipped, logsize, r->name, r->build) + db->stmt("UPDATE builds SET completedAt = ?, result = ?, output = ?, outputLen = ?, metadata = ? WHERE name = ? AND number = ?") + .bind(completedAt, int(r->result), maybeZipped, logsize, r->getMetaDataJsonString(), r->name, r->build) .exec(); // notify clients @@ -782,7 +800,8 @@ void Laminar::handleRunFinished(Run * r) { .set("completed", completedAt) .set("started", r->startedAt) .set("result", to_string(r->result)) - .set("reason", r->reason()); + .set("reason", r->reason()) + .setJsonObject("metadata", r->getMetaDataJsonString()); j.startArray("artifacts"); populateArtifacts(j, r->name, r->build); j.EndArray(); diff --git a/src/laminar.h b/src/laminar.h index a983c09..d1fd02f 100644 --- a/src/laminar.h +++ b/src/laminar.h @@ -96,6 +96,9 @@ public: // Abort all running jobs void abortAll(); + // Store metadata for an run + bool tag(std::string job, uint buildNum, std::string key, std::string value); + private: bool loadConfiguration(); void loadCustomizations(); diff --git a/src/resources/index.html b/src/resources/index.html index b5e0e8f..f9de78f 100644 --- a/src/resources/index.html +++ b/src/resources/index.html @@ -176,13 +176,17 @@
Completed
{{formatDate(job.completed)}}
Duration
{{formatDuration(job.started, job.completed)}}
-
-
Artifacts
-
+
+
Artifacts
+
+
diff --git a/src/resources/style.css b/src/resources/style.css index 290b0e6..1a44017 100644 --- a/src/resources/style.css +++ b/src/resources/style.css @@ -318,6 +318,7 @@ button:not([disabled]) { cursor: pointer; color: var(--main-fg); } #page-run-detail { display: grid; grid-template-columns: minmax(400px, auto) 1fr; + align-items: start; gap: 5px; } @media (max-width: 780px) { diff --git a/src/rpc.cpp b/src/rpc.cpp index dcd8338..5c3d76b 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -143,6 +143,19 @@ public: return kj::READY_NOW; } + kj::Promise 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: // Helper to convert an RPC parameter list to a hash map ParamMap params(const capnp::List::Reader& paramReader) { diff --git a/src/run.cpp b/src/run.cpp index 0a1feaf..af3af3b 100644 --- a/src/run.cpp +++ b/src/run.cpp @@ -208,3 +208,20 @@ bool Run::abort() { } 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()) ); +} diff --git a/src/run.h b/src/run.h index 7e9e67f..e17f8e2 100644 --- a/src/run.h +++ b/src/run.h @@ -19,7 +19,10 @@ #ifndef LAMINAR_RUN_H_ #define LAMINAR_RUN_H_ +#include "json.h" + #include +#include #include #include #include @@ -62,6 +65,11 @@ public: // aborts this run 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; kj::Promise whenStarted() { return startedFork.addBranch(); } @@ -101,6 +109,8 @@ private: kj::ForkedPromise startedFork; kj::PromiseFulfillerPair finished; kj::ForkedPromise finishedFork; + + std::map metaDataMap; }; // All this below is a somewhat overengineered method of keeping track of