mirror of
https://github.com/ohwgiles/laminar.git
synced 2026-10-13 14:41:02 +00:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9521ba0bab | ||
|
|
82b39c2961 | ||
|
|
1eb6a9138e | ||
|
|
e7fe117426 | ||
|
|
99fd47de68 | ||
|
|
f181c87d3b | ||
|
|
87ebf91f1e | ||
|
|
10d7ad8960 | ||
|
|
a83cc6c7a5 | ||
|
|
7b010fdaf6 | ||
|
|
645ed81aea | ||
|
|
e40fb52554 | ||
|
|
1273b36260 |
@@ -84,7 +84,7 @@ generate_compressed_bins(${CMAKE_BINARY_DIR} js/angular.min.js js/angular-route.
|
||||
add_executable(laminard src/database.cpp src/main.cpp src/server.cpp src/laminar.cpp
|
||||
src/conf.cpp src/resources.cpp src/run.cpp laminar.capnp.c++ ${COMPRESSED_BINS})
|
||||
# TODO: some alternative to boost::filesystem?
|
||||
target_link_libraries(laminard capnp-rpc capnp kj-async kj pthread boost_filesystem boost_system sqlite3)
|
||||
target_link_libraries(laminard capnp-rpc capnp kj-async kj pthread boost_filesystem boost_system sqlite3 z)
|
||||
|
||||
## Client
|
||||
add_executable(laminarc src/client.cpp laminar.capnp.c++)
|
||||
|
||||
80
docker-build-centos.sh
Executable file
80
docker-build-centos.sh
Executable file
@@ -0,0 +1,80 @@
|
||||
#!/bin/bash
|
||||
|
||||
VERSION=0.2
|
||||
|
||||
OUTPUT_DIR=$PWD
|
||||
|
||||
SOURCE_DIR=$(readlink -f $(dirname ${BASH_SOURCE[0]}))
|
||||
|
||||
docker run --rm -i -v $SOURCE_DIR:/root/rpmbuild/SOURCES/laminar-$VERSION:ro -v $OUTPUT_DIR:/output centos bash -xe <<EOS
|
||||
|
||||
yum -y install rpm-build cmake make gcc gcc-c++ wget sqlite-devel boost-devel zlib-devel
|
||||
|
||||
mkdir /build
|
||||
cd /build
|
||||
|
||||
wget -O capnproto.tar.gz https://github.com/sandstorm-io/capnproto/archive/v0.5.3.tar.gz
|
||||
wget -O websocketpp.tar.gz https://github.com/zaphoyd/websocketpp/archive/0.6.0.tar.gz
|
||||
wget -O rapidjson.tar.gz https://github.com/miloyip/rapidjson/archive/v1.0.2.tar.gz
|
||||
md5sum -c <<EOF
|
||||
909bd13ad6b8bc840ac78ab8f5bcb0a4 capnproto.tar.gz
|
||||
5a485884c01f881aafbf1e055d851b82 websocketpp.tar.gz
|
||||
97cc60d01282a968474c97f60714828c rapidjson.tar.gz
|
||||
EOF
|
||||
|
||||
tar xzf capnproto.tar.gz
|
||||
tar xzf websocketpp.tar.gz
|
||||
tar xzf rapidjson.tar.gz
|
||||
|
||||
cd /build/capnproto-0.5.3/c++/
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=off .
|
||||
make -j4
|
||||
make install
|
||||
|
||||
cd /build/websocketpp-0.6.0/
|
||||
cmake .
|
||||
make install
|
||||
|
||||
cd /build/rapidjson-1.0.2/
|
||||
cmake -DRAPIDJSON_BUILD_EXAMPLES=off .
|
||||
make install
|
||||
|
||||
cd
|
||||
cat <<EOF > laminar.spec
|
||||
Summary: Lightweight Continuous Integration Service
|
||||
Name: laminar
|
||||
Version: $VERSION
|
||||
Release: 1
|
||||
License: GPL
|
||||
BuildRequires: systemd-units
|
||||
Requires: boost-filesystem zlib
|
||||
|
||||
%description
|
||||
Lightweight Continuous Integration Service
|
||||
|
||||
%prep
|
||||
|
||||
%build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/ %{_sourcedir}/laminar-$VERSION
|
||||
pwd
|
||||
make
|
||||
|
||||
%install
|
||||
%make_install
|
||||
|
||||
%files
|
||||
%{_bindir}/laminarc
|
||||
%{_bindir}/laminard
|
||||
%{_unitdir}/laminar.service
|
||||
%config(noreplace) %{_sysconfdir}/laminar.conf
|
||||
|
||||
%post
|
||||
echo Creating laminar user with home in %{_sharedstatedir}/laminar
|
||||
useradd -r -d %{_sharedstatedir}/laminar -s %{_sbindir}/nologin laminar
|
||||
mkdir -p %{_sharedstatedir}/laminar/cfg/{jobs,nodes,scripts}
|
||||
chown -R laminar: %{_sharedstatedir}/laminar
|
||||
EOF
|
||||
|
||||
rpmbuild -ba laminar.spec
|
||||
mv rpmbuild/RPMS/x86_64/laminar-$VERSION-1.x86_64.rpm /output/
|
||||
EOS
|
||||
71
docker-build-ubuntu.sh
Executable file
71
docker-build-ubuntu.sh
Executable file
@@ -0,0 +1,71 @@
|
||||
#!/bin/bash
|
||||
|
||||
VERSION=0.2
|
||||
|
||||
OUTPUT_DIR=$PWD
|
||||
|
||||
SOURCE_DIR=$(readlink -f $(dirname ${BASH_SOURCE[0]}))
|
||||
|
||||
docker run --rm -i -v $SOURCE_DIR:/laminar:ro -v $OUTPUT_DIR:/output ubuntu bash -xe <<EOS
|
||||
|
||||
apt-get update
|
||||
apt-get install -y wget cmake g++ libsqlite3-dev libboost-filesystem1.55-dev zlib1g-dev
|
||||
|
||||
mkdir /build
|
||||
cd /build
|
||||
|
||||
wget -O capnproto.tar.gz https://github.com/sandstorm-io/capnproto/archive/v0.5.3.tar.gz
|
||||
wget -O websocketpp.tar.gz https://github.com/zaphoyd/websocketpp/archive/0.6.0.tar.gz
|
||||
wget -O rapidjson.tar.gz https://github.com/miloyip/rapidjson/archive/v1.0.2.tar.gz
|
||||
md5sum -c <<EOF
|
||||
909bd13ad6b8bc840ac78ab8f5bcb0a4 capnproto.tar.gz
|
||||
5a485884c01f881aafbf1e055d851b82 websocketpp.tar.gz
|
||||
97cc60d01282a968474c97f60714828c rapidjson.tar.gz
|
||||
EOF
|
||||
|
||||
tar xzf capnproto.tar.gz
|
||||
tar xzf websocketpp.tar.gz
|
||||
tar xzf rapidjson.tar.gz
|
||||
|
||||
cd /build/capnproto-0.5.3/c++/
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=off .
|
||||
make -j4
|
||||
make install
|
||||
|
||||
cd /build/websocketpp-0.6.0/
|
||||
cmake .
|
||||
make install
|
||||
|
||||
cd /build/rapidjson-1.0.2/
|
||||
cmake -DRAPIDJSON_BUILD_EXAMPLES=off .
|
||||
make install
|
||||
|
||||
cd /build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/ /laminar
|
||||
make -j4
|
||||
mkdir laminar
|
||||
make DESTDIR=laminar install/strip
|
||||
|
||||
mkdir laminar/DEBIAN
|
||||
cat <<EOF > laminar/DEBIAN/control
|
||||
Package: laminar
|
||||
Version: $VERSION
|
||||
Section:
|
||||
Priority: optional
|
||||
Architecture: amd64
|
||||
Maintainer: Oliver Giles <web ohwg net>
|
||||
Depends: libboost-filesystem1.55.0, zlib1g
|
||||
Description: Lightweight Continuous Integration Service
|
||||
EOF
|
||||
cat <<EOF > laminar/DEBIAN/postinst
|
||||
#!/bin/bash
|
||||
echo Creating laminar user with home in /var/lib/laminar
|
||||
useradd -r -d /var/lib/laminar -s /usr/sbin/nologin laminar
|
||||
mkdir -p /var/lib/laminar/cfg/{jobs,nodes,scripts}
|
||||
chown -R laminar: /var/lib/laminar
|
||||
EOF
|
||||
chmod +x laminar/DEBIAN/postinst
|
||||
|
||||
dpkg-deb --build laminar
|
||||
mv laminar.deb /output/laminar_$VERSION-1_amd64.deb
|
||||
EOS
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "database.h"
|
||||
|
||||
#include <sqlite3.h>
|
||||
#include <string.h>
|
||||
|
||||
Database::Database(const char *path) {
|
||||
sqlite3_open(path, &hdl);
|
||||
@@ -47,15 +48,17 @@ void Database::Statement::bindValue(int i, int e) {
|
||||
|
||||
void Database::Statement::bindValue(int i, const char* e) {
|
||||
sqlite3_bind_text(stmt, i, e, -1, NULL);
|
||||
|
||||
}
|
||||
|
||||
void Database::Statement::bindValue(int i, std::string e) {
|
||||
sqlite3_bind_text(stmt, i, e.c_str(), e.length(), NULL);
|
||||
sqlite3_bind_blob(stmt, i, e.data(), e.size(), NULL);
|
||||
}
|
||||
|
||||
template<> std::string Database::Statement::fetchColumn(int col) {
|
||||
return (char*)sqlite3_column_text(stmt, col);
|
||||
int sz = sqlite3_column_bytes(stmt, col);
|
||||
std::string res(sz, '\0');
|
||||
memcpy(&res[0], sqlite3_column_blob(stmt, col), sz);
|
||||
return res;
|
||||
}
|
||||
|
||||
template<> const char* Database::Statement::fetchColumn(int col) {
|
||||
|
||||
@@ -19,10 +19,11 @@
|
||||
#include "laminar.h"
|
||||
#include "server.h"
|
||||
#include "conf.h"
|
||||
#include "log.h"
|
||||
|
||||
#include <sys/wait.h>
|
||||
#include <fstream>
|
||||
#include <kj/debug.h>
|
||||
#include <zlib.h>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
namespace fs = boost::filesystem;
|
||||
@@ -69,7 +70,12 @@ Laminar::Laminar() {
|
||||
// Prepare database for first use
|
||||
// TODO: error handling
|
||||
db->exec("CREATE TABLE IF NOT EXISTS builds("
|
||||
"name TEXT, number INT UNSIGNED, node TEXT, queuedAt INT, startedAt INT, completedAt INT, result INT, output TEXT, parentJob TEXT, parentBuild INT, reason TEXT)");
|
||||
"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))");
|
||||
db->exec("CREATE INDEX IF NOT EXISTS idx_completion_time ON builds("
|
||||
"completedAt DESC)");
|
||||
|
||||
// retrieve the last build numbers
|
||||
db->stmt("SELECT name, MAX(number) FROM builds GROUP BY name")
|
||||
@@ -123,10 +129,16 @@ void Laminar::sendStatus(LaminarClient* client) {
|
||||
if(const Run* run = activeRun(client->scope.job, client->scope.num)) {
|
||||
client->sendMessage(run->log.c_str());
|
||||
} else { // it must be finished, fetch it from the database
|
||||
db->stmt("SELECT output FROM builds WHERE name = ? AND number = ?")
|
||||
db->stmt("SELECT output, outputLen FROM builds WHERE name = ? AND number = ?")
|
||||
.bind(client->scope.job, client->scope.num)
|
||||
.fetch<const char*>([=](const char* log) {
|
||||
client->sendMessage(log);
|
||||
.fetch<str,int>([=](str zipped, unsigned long sz) {
|
||||
std::string log(sz+1,'\0');
|
||||
int res = ::uncompress((unsigned char*)&log[0], &sz,
|
||||
(unsigned char*)zipped.data(), zipped.size());
|
||||
if(res == Z_OK)
|
||||
client->sendMessage(log);
|
||||
else
|
||||
LLOG(ERROR, "Failed to uncompress log", res);
|
||||
});
|
||||
}
|
||||
return;
|
||||
@@ -151,6 +163,7 @@ void Laminar::sendStatus(LaminarClient* client) {
|
||||
j.set("queued", run->startedAt - run->queuedAt);
|
||||
j.set("started", run->startedAt);
|
||||
j.set("reason", run->reason());
|
||||
j.set("result", to_string(RunState::RUNNING));
|
||||
db->stmt("SELECT completedAt - startedAt FROM builds WHERE name = ? ORDER BY completedAt DESC LIMIT 1")
|
||||
.bind(run->name)
|
||||
.fetch<int>([&](int etc){
|
||||
@@ -183,6 +196,7 @@ void Laminar::sendStatus(LaminarClient* client) {
|
||||
j.set("number", run->build);
|
||||
j.set("node", run->node->name);
|
||||
j.set("started", run->startedAt);
|
||||
j.set("result", to_string(RunState::RUNNING));
|
||||
j.set("reason", run->reason());
|
||||
j.EndObject();
|
||||
}
|
||||
@@ -397,14 +411,14 @@ bool Laminar::loadConfiguration() {
|
||||
|
||||
std::shared_ptr<Run> Laminar::queueJob(std::string name, ParamMap params) {
|
||||
if(!fs::exists(fs::path(homeDir)/"cfg"/"jobs"/name)) {
|
||||
KJ_LOG(ERROR, "Non-existent job", name);
|
||||
LLOG(ERROR, "Non-existent job", name);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// attempt to create a workspace for this job if it doesn't exist
|
||||
if(!fs::exists(fs::path(homeDir)/"run"/name/"workspace")) {
|
||||
if(!fs::create_directories(fs::path(homeDir)/"run"/name/"workspace")) {
|
||||
KJ_LOG(ERROR, "Could not create job workspace", name);
|
||||
LLOG(ERROR, "Could not create job workspace", name);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
@@ -421,7 +435,7 @@ std::shared_ptr<Run> Laminar::queueJob(std::string name, ParamMap params) {
|
||||
} else if(it->first == "=reason") {
|
||||
run->reasonMsg = it->second;
|
||||
} else {
|
||||
KJ_LOG(ERROR, "Unknown internal job parameter", it->first);
|
||||
LLOG(ERROR, "Unknown internal job parameter", it->first);
|
||||
}
|
||||
it = params.erase(it);
|
||||
} else
|
||||
@@ -479,7 +493,7 @@ void Laminar::reapAdvance() {
|
||||
pid_t pid = waitpid(-1, &ret, 0);
|
||||
// TODO: handle signalled child processes
|
||||
if(pid > 0) {
|
||||
KJ_LOG(INFO, "Reaping", pid);
|
||||
LLOG(INFO, "Reaping", pid);
|
||||
auto it = activeJobs.get<0>().find(pid);
|
||||
std::shared_ptr<Run> run = *it;
|
||||
bool completed = true;
|
||||
@@ -530,14 +544,16 @@ void Laminar::assignNewJobs() {
|
||||
// create a working directory (different to a workspace!)
|
||||
fs::path wd = fs::path(homeDir)/"run"/run->name/std::to_string(buildNum);
|
||||
if(!fs::create_directory(wd)) {
|
||||
KJ_LOG(ERROR, "Could not create working directory", wd.string());
|
||||
LLOG(ERROR, "Could not create working directory", wd.string());
|
||||
break;
|
||||
}
|
||||
run->wd = wd.string();
|
||||
// create an archive directory
|
||||
fs::path archive = fs::path(homeDir)/"archive"/run->name/std::to_string(buildNum);
|
||||
if(!fs::create_directories(archive)) {
|
||||
KJ_LOG(ERROR, "Could not create archive directory", archive.string());
|
||||
if(fs::is_directory(archive)) {
|
||||
LLOG(WARNING, "Archive directory already exists", archive.string());
|
||||
} else if(!fs::create_directories(archive)) {
|
||||
LLOG(ERROR, "Could not create archive directory", archive.string());
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -587,7 +603,7 @@ void Laminar::assignNewJobs() {
|
||||
// update next build number
|
||||
buildNums[run->name] = buildNum;
|
||||
|
||||
KJ_LOG(INFO, "Queued job to node", run->name, run->build, node.name);
|
||||
LLOG(INFO, "Queued job to node", run->name, run->build, node.name);
|
||||
|
||||
// notify clients
|
||||
Json j;
|
||||
@@ -616,12 +632,12 @@ void Laminar::assignNewJobs() {
|
||||
}
|
||||
|
||||
// setup run completion handler
|
||||
run->notifyCompletion = [this](const Run* r) { runFinished(r); };
|
||||
run->notifyCompletion = [this](Run* r) { runFinished(r); };
|
||||
|
||||
// trigger the first step of the run
|
||||
if(stepRun(run)) {
|
||||
// should never happen
|
||||
KJ_LOG(INFO, "No steps for run");
|
||||
LLOG(INFO, "No steps for run");
|
||||
run->complete();
|
||||
}
|
||||
|
||||
@@ -638,15 +654,24 @@ void Laminar::assignNewJobs() {
|
||||
}
|
||||
}
|
||||
|
||||
void Laminar::runFinished(const Run * r) {
|
||||
void Laminar::runFinished(Run * r) {
|
||||
Node* node = r->node;
|
||||
|
||||
node->busyExecutors--;
|
||||
KJ_LOG(INFO, "Run completed", r->name, to_string(r->result));
|
||||
LLOG(INFO, "Run completed", r->name, to_string(r->result));
|
||||
time_t completedAt = time(0);
|
||||
db->stmt("INSERT INTO builds VALUES(?,?,?,?,?,?,?,?,?,?,?)")
|
||||
|
||||
// compress log
|
||||
std::string zipped(r->log.size(), '\0');
|
||||
size_t logsize = r->log.length();
|
||||
size_t zippedSize = zipped.size();
|
||||
::compress((unsigned char*)&zipped[0], &zippedSize,
|
||||
(unsigned char*)&r->log[0], logsize);
|
||||
zipped.resize(zippedSize);
|
||||
|
||||
db->stmt("INSERT INTO builds VALUES(?,?,?,?,?,?,?,?,?,?,?,?)")
|
||||
.bind(r->name, r->build, node->name, r->queuedAt, r->startedAt, completedAt, int(r->result),
|
||||
r->log, r->parentName, r->parentBuild, r->reason())
|
||||
zipped, logsize, r->parentName, r->parentBuild, r->reason())
|
||||
.exec();
|
||||
|
||||
// notify clients
|
||||
|
||||
@@ -61,7 +61,7 @@ private:
|
||||
void reapAdvance();
|
||||
void assignNewJobs();
|
||||
bool stepRun(std::shared_ptr<Run> run);
|
||||
void runFinished(const Run*);
|
||||
void runFinished(Run*);
|
||||
bool nodeCanQueue(const Node&, const Run&) const;
|
||||
// expects that Json has started an array
|
||||
void populateArtifacts(Json& out, std::string job, int num) const;
|
||||
|
||||
37
src/log.h
Normal file
37
src/log.h
Normal file
@@ -0,0 +1,37 @@
|
||||
///
|
||||
/// Copyright 2015 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_LOG_H_
|
||||
#define _LAMINAR_LOG_H_
|
||||
|
||||
#include <kj/debug.h>
|
||||
|
||||
// Simple override to prevent full paths to source files from
|
||||
// appearing in log messages. Assumes / is the path separator.
|
||||
// @see kj/debug.h
|
||||
|
||||
#define _LBASENAME(path) strrchr(path, '/') ? strrchr(path, '/') + 1 : path
|
||||
|
||||
#define LLOG(severity, ...) \
|
||||
if (!::kj::_::Debug::shouldLog(::kj::_::Debug::Severity::severity)) {} else \
|
||||
::kj::_::Debug::log(_LBASENAME(__FILE__), __LINE__, ::kj::_::Debug::Severity::severity, \
|
||||
#__VA_ARGS__, __VA_ARGS__)
|
||||
|
||||
|
||||
#endif // _LAMINAR_LOG_H_
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
/// along with Laminar. If not, see <http://www.gnu.org/licenses/>
|
||||
///
|
||||
#include "laminar.h"
|
||||
#include "log.h"
|
||||
|
||||
#include <signal.h>
|
||||
#include <kj/debug.h>
|
||||
|
||||
std::function<void()> sigHandler;
|
||||
static void __sigHandler(int) { sigHandler(); }
|
||||
@@ -34,7 +34,7 @@ int main(int argc, char** argv) {
|
||||
do {
|
||||
Laminar laminar;
|
||||
sigHandler = [&](){
|
||||
KJ_LOG(INFO, "Received SIGINT");
|
||||
LLOG(INFO, "Received SIGINT");
|
||||
laminar.stop();
|
||||
};
|
||||
signal(SIGINT, &__sigHandler);
|
||||
@@ -43,7 +43,7 @@ int main(int argc, char** argv) {
|
||||
laminar.run();
|
||||
} while(false);
|
||||
|
||||
KJ_DBG("end of main");
|
||||
LLOG(INFO, "end of main");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -213,20 +213,20 @@ angular.module('laminar',['ngRoute','ngSanitize'])
|
||||
$scope.$apply();
|
||||
|
||||
var chtBt = new Chart(document.getElementById("chartBt").getContext("2d")).Bar({
|
||||
labels: data.recent.map(function(e){return '#' + e.number;}),
|
||||
labels: data.recent.map(function(e){return '#' + e.number;}).reverse(),
|
||||
datasets: [{
|
||||
fillColor: "darkseagreen",
|
||||
strokeColor: "forestgreen",
|
||||
data: data.recent.map(function(e){return e.duration;})
|
||||
data: data.recent.map(function(e){return e.duration;}).reverse()
|
||||
}]
|
||||
},
|
||||
{barValueSpacing: 1,barStrokeWidth: 1,barDatasetSpacing:0}
|
||||
);
|
||||
|
||||
for(var i = 0; i < data.recent.length; ++i) {
|
||||
for(var i = 0, n = data.recent.length; i < n; ++i) {
|
||||
if(data.recent[i].result != "success") {
|
||||
chtBt.datasets[0].bars[i].fillColor = "darksalmon";
|
||||
chtBt.datasets[0].bars[i].strokeColor = "crimson";
|
||||
chtBt.datasets[0].bars[n-i-1].fillColor = "darksalmon";
|
||||
chtBt.datasets[0].bars[n-i-1].strokeColor = "crimson";
|
||||
}
|
||||
}
|
||||
chtBt.update();
|
||||
@@ -306,7 +306,10 @@ angular.module('laminar',['ngRoute','ngSanitize'])
|
||||
.run(function($rootScope) {
|
||||
angular.extend($rootScope, {
|
||||
runIcon: function(result) {
|
||||
return result === "success" ? '<span style="color:forestgreen;font-family:\'Zapf Dingbats\';">✔</span>' : result === "failed" ? '<span style="color:crimson;">✘</span>' : '';
|
||||
return result === "success" ? '<span style="color:forestgreen;font-family:\'Zapf Dingbats\';">✔</span>' : result === "failed" || result === "aborted" ? '<span style="color:crimson;">✘</span>' : '';
|
||||
},
|
||||
runComplete: function(run) {
|
||||
return !!run && (run.result === 'aborted' || run.result === 'failed' || run.result === 'success');
|
||||
},
|
||||
formatDate: function(unix) {
|
||||
// TODO reimplement when toLocaleDateString() accepts formatting
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-sm-5 col-md-6 col-lg-7">
|
||||
<h3 style="float:left"><img class="spin" src="/progress.png" ng-hide="job.result"><span ng-bind-html="runIcon(job.result)"></span> {{name}} #{{num}}</h3>
|
||||
<h3 style="float:left"><img class="spin" src="/progress.png" ng-show="job.result === 'running'"><span ng-bind-html="runIcon(job.result)"></span> {{name}} #{{num}}</h3>
|
||||
<nav class="pull-left">
|
||||
<ul class="pagination" style="margin:15px 20px">
|
||||
<li><a href="jobs/{{name}}/{{num-1}}">«</a></li>
|
||||
<li ng-show="num > 1"><a href="jobs/{{name}}/{{num-1}}">«</a></li>
|
||||
<li ng-show="latestNum > num"><a ng-href="jobs/{{name}}/{{num+1}}">»</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
@@ -13,12 +13,12 @@
|
||||
<dt>Reason</dt><dd>{{job.reason}}</dd>
|
||||
<dt>Queued for</dt><dd>{{job.queued}}s</dd>
|
||||
<dt>Started</dt><dd>{{formatDate(job.started)}}</dd>
|
||||
<dt ng-show="job.result">Completed</dt><dd ng-show="job.result">{{formatDate(job.completed)}}</dd>
|
||||
<dt ng-show="job.result">Duration</dt><dd ng-show="job.result">{{job.duration}}s</dd>
|
||||
<dt ng-show="runComplete(job)">Completed</dt><dd ng-show="runComplete(job)">{{formatDate(job.completed)}}</dd>
|
||||
<dt ng-show="runComplete(job)">Duration</dt><dd ng-show="runComplete(job)">{{job.duration}}s</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="col-sm-7 col-md-6 col-lg-5">
|
||||
<div class="progress" ng-hide="job.result">
|
||||
<div class="progress" ng-show="job.result == 'running'">
|
||||
<div class="progress-bar progress-bar-{{job.overtime?'warning':'info'}} progress-bar-striped {{job.etc?'':'active'}}" style="width:{{!job.etc?'100':job.progress}}%;"></div>
|
||||
</div>
|
||||
<div class="panel panel-default" ng-show="job.artifacts.length">
|
||||
|
||||
14
src/run.cpp
14
src/run.cpp
@@ -19,9 +19,9 @@
|
||||
#include "run.h"
|
||||
#include "node.h"
|
||||
#include "conf.h"
|
||||
#include <iostream>
|
||||
#include <kj/debug.h>
|
||||
#include "log.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
@@ -30,6 +30,7 @@ namespace fs = boost::filesystem;
|
||||
std::string to_string(const RunState& rs) {
|
||||
switch(rs) {
|
||||
case RunState::PENDING: return "pending";
|
||||
case RunState::RUNNING: return "running";
|
||||
case RunState::ABORTED: return "aborted";
|
||||
case RunState::FAILED: return "failed";
|
||||
case RunState::SUCCESS: return "success";
|
||||
@@ -46,7 +47,7 @@ Run::Run() {
|
||||
}
|
||||
|
||||
Run::~Run() {
|
||||
KJ_DBG("Run destroyed");
|
||||
LLOG(INFO, "Run destroyed");
|
||||
}
|
||||
|
||||
std::string Run::reason() const {
|
||||
@@ -100,13 +101,14 @@ bool Run::step() {
|
||||
for(auto& pair : params) {
|
||||
setenv(pair.first.c_str(), pair.second.c_str(), false);
|
||||
}
|
||||
printf("[laminar] Executing %s\n", currentScript.c_str());
|
||||
fprintf(stderr, "[laminar] Executing %s\n", currentScript.c_str());
|
||||
execl(currentScript.c_str(), currentScript.c_str(), NULL);
|
||||
KJ_LOG(FATAL, "execl returned", strerror(errno));
|
||||
// cannot use LLOG because stdout/stderr are captured
|
||||
fprintf(stderr, "[laminar] Failed to execute %s\n", currentScript.c_str());
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
KJ_LOG(INFO, "Forked", currentScript, pid);
|
||||
LLOG(INFO, "Forked", currentScript, pid);
|
||||
close(pfd[1]);
|
||||
fd = pfd[0];
|
||||
this->pid = pid;
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
enum class RunState {
|
||||
UNKNOWN,
|
||||
PENDING,
|
||||
RUNNING,
|
||||
ABORTED,
|
||||
FAILED,
|
||||
SUCCESS
|
||||
@@ -67,7 +68,7 @@ public:
|
||||
|
||||
std::string reason() const;
|
||||
|
||||
std::function<void(const Run*)> notifyCompletion;
|
||||
std::function<void(Run*)> notifyCompletion;
|
||||
Node* node;
|
||||
RunState result;
|
||||
RunState lastResult;
|
||||
|
||||
@@ -20,12 +20,12 @@
|
||||
#include "interface.h"
|
||||
#include "laminar.capnp.h"
|
||||
#include "resources.h"
|
||||
#include "log.h"
|
||||
|
||||
#include <capnp/ez-rpc.h>
|
||||
#include <capnp/rpc-twoparty.h>
|
||||
#include <capnp/rpc.capnp.h>
|
||||
#include <kj/async-io.h>
|
||||
#include <kj/debug.h>
|
||||
#include <kj/threadlocal.h>
|
||||
|
||||
#include <websocketpp/config/core.hpp>
|
||||
@@ -58,9 +58,9 @@ namespace {
|
||||
LaminarCi::JobResult fromRunState(RunState state) {
|
||||
switch(state) {
|
||||
case RunState::SUCCESS: return LaminarCi::JobResult::SUCCESS;
|
||||
case RunState::FAILED: return LaminarCi::JobResult::FAILED;
|
||||
case RunState::FAILED: return LaminarCi::JobResult::FAILED;
|
||||
case RunState::ABORTED: return LaminarCi::JobResult::ABORTED;
|
||||
default:
|
||||
KJ_DBG("TODO log state", to_string(state));
|
||||
return LaminarCi::JobResult::UNKNOWN;
|
||||
}
|
||||
}
|
||||
@@ -81,7 +81,7 @@ public:
|
||||
// Start a job, without waiting for it to finish
|
||||
kj::Promise<void> trigger(TriggerContext context) override {
|
||||
std::string jobName = context.getParams().getJobName();
|
||||
KJ_LOG(INFO, "RPC trigger", jobName);
|
||||
LLOG(INFO, "RPC trigger", jobName);
|
||||
ParamMap params;
|
||||
for(auto p : context.getParams().getParams()) {
|
||||
params[p.getName().cStr()] = p.getValue().cStr();
|
||||
@@ -96,7 +96,7 @@ public:
|
||||
// Start a job and wait for the result
|
||||
kj::Promise<void> start(StartContext context) override {
|
||||
std::string jobName = context.getParams().getJobName();
|
||||
KJ_LOG(INFO, "RPC start", jobName);
|
||||
LLOG(INFO, "RPC start", jobName);
|
||||
ParamMap params;
|
||||
for(auto p : context.getParams().getParams()) {
|
||||
params[p.getName().cStr()] = p.getValue().cStr();
|
||||
@@ -116,7 +116,7 @@ public:
|
||||
kj::Promise<void> pend(PendContext context) override {
|
||||
std::string jobName = context.getParams().getJobName();
|
||||
int buildNum = context.getParams().getBuildNum();
|
||||
KJ_LOG(INFO, "RPC pend", jobName, buildNum);
|
||||
LLOG(INFO, "RPC pend", jobName, buildNum);
|
||||
|
||||
kj::Promise<RunState> promise = laminar.waitForRun(jobName, buildNum);
|
||||
|
||||
@@ -129,7 +129,7 @@ public:
|
||||
kj::Promise<void> set(SetContext context) override {
|
||||
std::string jobName = context.getParams().getJobName();
|
||||
int buildNum = context.getParams().getBuildNum();
|
||||
KJ_LOG(INFO, "RPC set", jobName, buildNum);
|
||||
LLOG(INFO, "RPC set", jobName, buildNum);
|
||||
|
||||
LaminarCi::MethodResult result = laminar.setParam(jobName, buildNum,
|
||||
context.getParams().getParam().getName(), context.getParams().getParam().getValue())
|
||||
|
||||
Reference in New Issue
Block a user