Initial commit
This commit is contained in:
commit
3cc2460a9e
4
README.MD
Normal file
4
README.MD
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Micobu
|
||||||
|
Mini Continous Build
|
||||||
|
|
||||||
|
A tiny continuous build system written in bash and php.
|
82
gitea.php
Normal file
82
gitea.php
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// Micobu Gitea Hook
|
||||||
|
|
||||||
|
$secret_key = 'mykey';
|
||||||
|
$micobu_pipe = '/var/tmp/micobu_pipe';
|
||||||
|
$micobu_key = "mykey";
|
||||||
|
|
||||||
|
// check for POST request
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] != 'POST') {
|
||||||
|
error_log('GITEA HOOK: FAILED - not POST - '. $_SERVER['REQUEST_METHOD']);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// get content type
|
||||||
|
$content_type = isset($_SERVER['CONTENT_TYPE']) ? strtolower(trim($_SERVER['CONTENT_TYPE'])) : '';
|
||||||
|
|
||||||
|
if ($content_type != 'application/json') {
|
||||||
|
error_log('GITEA HOOK: FAILED - not application/json - '. $content_type);
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// get payload
|
||||||
|
$payload = trim(file_get_contents("php://input"));
|
||||||
|
|
||||||
|
if (empty($payload)) {
|
||||||
|
error_log('GITEA HOOK: FAILED - no payload');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// get header signature
|
||||||
|
$header_signature = isset($_SERVER['HTTP_X_GITEA_SIGNATURE']) ? $_SERVER['HTTP_X_GITEA_SIGNATURE'] : '';
|
||||||
|
|
||||||
|
if (empty($header_signature)) {
|
||||||
|
error_log('GITEA HOOK: FAILED - header signature missing');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate payload signature
|
||||||
|
$payload_signature = hash_hmac('sha256', $payload, $secret_key, false);
|
||||||
|
|
||||||
|
// check payload signature against header signature
|
||||||
|
if ($header_signature !== $payload_signature) {
|
||||||
|
error_log('GITEA HOOK: FAILED - payload signature');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert json to array
|
||||||
|
$decoded = json_decode($payload);
|
||||||
|
|
||||||
|
// check for json decode errors
|
||||||
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
|
error_log('GITEA HOOK: FAILED - json decode - '. json_last_error());
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// success, do something
|
||||||
|
$name = $decoded->repository->name;
|
||||||
|
$url = $decoded->repository->html_url;
|
||||||
|
$hash = $decoded->after;
|
||||||
|
|
||||||
|
preg_match("/^[_\-A-Za-z0-9]{1,30}$/", $name, $matches);
|
||||||
|
if (!$matches) {
|
||||||
|
error_log('GITEA HOOK: FAILED - name verification');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
preg_match("/^[_\-A-Za-z0-9:\/\.]{1,150}$/", $url, $matches);
|
||||||
|
if (!$matches) {
|
||||||
|
error_log('GITEA HOOK: FAILED - url verification');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
preg_match("/^[A-Za-z0-9]{1,50}$/", $hash, $matches);
|
||||||
|
if (!$matches) {
|
||||||
|
error_log('GITEA HOOK: FAILED - hash verification');
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
file_put_contents($micobu_pipe, "$micobu_key $name $url $hash\n");
|
||||||
|
|
||||||
|
?>
|
192
index.php
Normal file
192
index.php
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
<?php
|
||||||
|
define("MICOBU_BASE", "/opt/micobu/rootfs/home/builduser/artefacts");
|
||||||
|
define("MICOBU_BASE_LINK", "./artefacts");
|
||||||
|
|
||||||
|
class micobu_build {
|
||||||
|
public $hash;
|
||||||
|
public $result;
|
||||||
|
public $time_start;
|
||||||
|
public $time_stop;
|
||||||
|
public $project;
|
||||||
|
}
|
||||||
|
class micobu_project {
|
||||||
|
public $name;
|
||||||
|
public $builds;
|
||||||
|
}
|
||||||
|
|
||||||
|
function glob_sorted_mtime($dir) {
|
||||||
|
$ar = [];
|
||||||
|
array_multisort(array_map('filemtime', ($files = glob("$dir/*", GLOB_ONLYDIR))), SORT_DESC, $files);
|
||||||
|
foreach($files as $filename)
|
||||||
|
{
|
||||||
|
$ar[] = $filename;
|
||||||
|
}
|
||||||
|
return $ar;
|
||||||
|
}
|
||||||
|
|
||||||
|
function scan_build(&$project, $hash) {
|
||||||
|
$build = new micobu_build();
|
||||||
|
|
||||||
|
$build->hash = $hash;
|
||||||
|
$build->result = -1;
|
||||||
|
$build->project = $project;
|
||||||
|
|
||||||
|
$file = MICOBU_BASE . "/". $project->name . "/" . $hash . "/meta.json";
|
||||||
|
if (file_exists($file)) {
|
||||||
|
$json = json_decode(file_get_contents($file));
|
||||||
|
$build->hash = $json->hash;
|
||||||
|
$build->result = $json->result;
|
||||||
|
$build->time_start = $json->time_start;
|
||||||
|
$build->time_stop = $json->time_stop;
|
||||||
|
$build->description = $json->description;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $build;
|
||||||
|
}
|
||||||
|
|
||||||
|
function scan_project($name) {
|
||||||
|
$project = new micobu_project();
|
||||||
|
$project->name = $name;
|
||||||
|
$project->builds = array();
|
||||||
|
|
||||||
|
$dirs = glob_sorted_mtime(MICOBU_BASE . "/" . $name);
|
||||||
|
foreach ($dirs as $dir) {
|
||||||
|
$hash = basename($dir);
|
||||||
|
$project->builds += array($hash => scan_build($project, $hash));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $project;
|
||||||
|
}
|
||||||
|
|
||||||
|
function micobu_scan() {
|
||||||
|
$projects = array();
|
||||||
|
foreach (glob(MICOBU_BASE . "/*", GLOB_ONLYDIR) as $project_path) {
|
||||||
|
$project_name = basename($project_path);
|
||||||
|
$projects += array($project_name => scan_project($project_name));
|
||||||
|
}
|
||||||
|
return $projects;
|
||||||
|
}
|
||||||
|
|
||||||
|
function micobu_get_trend($project) {
|
||||||
|
foreach ($project->builds as $build) {
|
||||||
|
if ($build->result > 0)
|
||||||
|
return "<span title='failed'>❌</span>";
|
||||||
|
else
|
||||||
|
return "<span title='success'>✅</span>";
|
||||||
|
}
|
||||||
|
return "⌛";
|
||||||
|
return "🔼";
|
||||||
|
return "❗";
|
||||||
|
}
|
||||||
|
|
||||||
|
function micobu_get_build_icon($build) {
|
||||||
|
if ($build->result > 0)
|
||||||
|
return "<span title='Build failed with return code $build->result'>❌</span>";
|
||||||
|
else
|
||||||
|
return "<span title='Build succeeded'>✅</span>";
|
||||||
|
}
|
||||||
|
|
||||||
|
function micobu_get_build_time($build) {
|
||||||
|
return gmdate("i\m s\s", $build->time_stop - $build->time_start);
|
||||||
|
}
|
||||||
|
|
||||||
|
function micobu_get_weblink($build, $filename) {
|
||||||
|
return MICOBU_BASE_LINK . "/" . $build->project->name . "/" . $build->hash . "/" . $filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
$projects = micobu_scan();
|
||||||
|
|
||||||
|
if (isset($_GET['p'])) {
|
||||||
|
$selected_project = $_GET['p'];
|
||||||
|
preg_match("/^[_\-A-Za-z0-9]{1,20}$/", $selected_project, $matches);
|
||||||
|
if (!$matches)
|
||||||
|
die("Invalid request.");
|
||||||
|
if (!isset($projects[$selected_project]))
|
||||||
|
die("Invalid project.");
|
||||||
|
|
||||||
|
if (isset($_GET['h'])) {
|
||||||
|
$selected_hash = $_GET['h'];
|
||||||
|
preg_match("/^[_\-A-Za-z0-9]{1,40}$/", $selected_hash, $matches);
|
||||||
|
if (!$matches)
|
||||||
|
die("Invalid request.");
|
||||||
|
if (!isset($projects[$selected_project]->builds[$selected_hash]))
|
||||||
|
die("Invalid hash.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<!--
|
||||||
|
Hi there, curious person!
|
||||||
|
|
||||||
|
This is the source code of Micobu's web dashboard.
|
||||||
|
You can find more information on this system on:
|
||||||
|
<link TBD>
|
||||||
|
-->
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: sans-serif;
|
||||||
|
background-color: #111;
|
||||||
|
color: #CCC;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
background-color: #222;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 20pt;
|
||||||
|
border: 1px solid black;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
tr:hover {
|
||||||
|
background-color: #555;
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
border: 1px dotted black;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
margin: 8px;
|
||||||
|
color: #666;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<?php
|
||||||
|
$dpath = (isset($selected_project) ? " / <a href='?p=$selected_project'>$selected_project</a>" : "");
|
||||||
|
$dpath .= (isset($selected_hash) ? " / $selected_hash" : "");
|
||||||
|
echo "<h1><a href='?'>Micobu CI Dashboard</a>$dpath</h1>";
|
||||||
|
?>
|
||||||
|
<div>
|
||||||
|
<?php
|
||||||
|
if (isset($selected_hash)) {
|
||||||
|
//var_dump($projects[$selected_project]->builds[$selected_hash]);
|
||||||
|
$build = $projects[$selected_project]->builds[$selected_hash];
|
||||||
|
echo "<table>";
|
||||||
|
echo "<tr><td>Build status</td><td>" . micobu_get_build_icon($build). "</td></tr>";
|
||||||
|
echo "<tr><td>Build time</td><td>" . micobu_get_build_time($build) . "</td></tr>";
|
||||||
|
echo "</table>";
|
||||||
|
} else if (isset($selected_project)) {
|
||||||
|
echo "<table>";
|
||||||
|
foreach ($projects[$selected_project]->builds as $hash => $build) {
|
||||||
|
echo "<tr><td><a href='?p=$selected_project&h=$hash'>" . date(DATE_RFC2822, $build->time_start) . "</a></td><td>" . micobu_get_build_icon($build) . "</td><td><a title='$build->hash'href='?p=$selected_project&h=$hash'>" . $build->description . "</a></td><td><a title='Show log file' href='" . micobu_get_weblink($build, "log.txt") . "'>📰</a> <a title='Show meta info' href='" . micobu_get_weblink($build, "meta.json") . "'>🛈</a> <a title='Show artefacts' href='" . micobu_get_weblink($build, "") . "'>📂</a></td></tr>";
|
||||||
|
}
|
||||||
|
echo "</table>";
|
||||||
|
} else {
|
||||||
|
echo "<table>";
|
||||||
|
foreach ($projects as $project) {
|
||||||
|
echo "<tr><td><a href='?p=" . $project->name ."'>" . $project->name . "</a></td><td>" . micobu_get_trend($project) . "</td></tr>";
|
||||||
|
}
|
||||||
|
echo "</table>";
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<?php
|
||||||
|
echo "Processed " . count($projects) . " repositories in " . round((microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"]) * 1000, 1) . " ms.";
|
||||||
|
?>
|
||||||
|
</div>
|
117
micobu.sh
Executable file
117
micobu.sh
Executable file
@ -0,0 +1,117 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Chroot instructions
|
||||||
|
# ArchLinux root via pacstrap, configured with build tools
|
||||||
|
# User: builduser 1000
|
||||||
|
|
||||||
|
BUILD_BASE='/opt/micobu/rootfs/home/builduser/base'
|
||||||
|
ARTEFACT_BASE='/opt/micobu/rootfs/home/builduser/artefacts'
|
||||||
|
BUILD_TIMEOUT='600'
|
||||||
|
API_KEY='mykey'
|
||||||
|
SERVER_PIPE='/var/tmp/micobu_pipe'
|
||||||
|
|
||||||
|
function build() {
|
||||||
|
NAME="$1"
|
||||||
|
REPO="$2"
|
||||||
|
HASH="$3"
|
||||||
|
|
||||||
|
BUILD_DIR="$BUILD_BASE/$NAME"
|
||||||
|
ARTEFACT_DIR="$ARTEFACT_BASE/$NAME/$HASH"
|
||||||
|
MICOBU_TEMP_NAME="micobu_$NAME_$HASH" # Relative to $BUILD_BASE
|
||||||
|
|
||||||
|
if [ -d "$ARTEFACT_DIR" ]; then
|
||||||
|
echo "$NAME revision $HASH has already been built."
|
||||||
|
echo "Removing old version"
|
||||||
|
rm -rf $ARTEFACT_DIR
|
||||||
|
#return
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[env] Building in $BUILD_DIR."
|
||||||
|
|
||||||
|
if [ ! -d "$BUILD_DIR" ]; then
|
||||||
|
echo "[git] Performing initial clone"
|
||||||
|
mkdir -p $BUILD_DIR
|
||||||
|
git clone --recurse-submodules "$REPO" "$BUILD_DIR"
|
||||||
|
cd $BUILD_DIR
|
||||||
|
else
|
||||||
|
echo "[git] Updating existing repo."
|
||||||
|
cd $BUILD_DIR
|
||||||
|
git fetch
|
||||||
|
fi
|
||||||
|
git clean -xdf
|
||||||
|
git reset --hard
|
||||||
|
git checkout $HASH
|
||||||
|
|
||||||
|
DESCRIPTION="`git describe --all`"
|
||||||
|
|
||||||
|
mkdir -p $BUILD_BASE/$MICOBU_TEMP_NAME
|
||||||
|
cat <<EOF > $BUILD_BASE/micobu_run.sh
|
||||||
|
#!/bin/bash
|
||||||
|
cd ~/$NAME
|
||||||
|
./run.py --no-color
|
||||||
|
RET=\$?
|
||||||
|
|
||||||
|
cd vunit_out/test_output
|
||||||
|
tar cvJf ~/$MICOBU_TEMP_NAME/artefacts.tar.xz *
|
||||||
|
|
||||||
|
exit \$RET
|
||||||
|
EOF
|
||||||
|
chmod +x $BUILD_BASE/micobu_run.sh
|
||||||
|
|
||||||
|
echo "[sec] Creating jail"
|
||||||
|
META_DATE_START="`date '+%s'`"
|
||||||
|
set +e
|
||||||
|
set -o pipefail
|
||||||
|
firejail --noprofile --chroot=/opt/micobu-rootfs --net=none --read-write="$BUILD_DIR" env -i HOME=/home/builduser PATH=/bin:/usr/bin "bash" "-c" "timeout $BUILD_TIMEOUT ~/micobu_run.sh" | tee $BUILD_BASE/$MICOBU_TEMP_NAME/log.txt
|
||||||
|
RET=$?
|
||||||
|
set +o pipefail
|
||||||
|
set -e
|
||||||
|
META_DATE_STOP="`date '+%s'`"
|
||||||
|
rm $BUILD_BASE/micobu_run.sh
|
||||||
|
|
||||||
|
cat <<EOF > $BUILD_BASE/$MICOBU_TEMP_NAME/meta.json
|
||||||
|
{
|
||||||
|
"name" : "$NAME",
|
||||||
|
"url" : "$REPO",
|
||||||
|
"hash" : "$HASH",
|
||||||
|
"description" : "$DESCRIPTION",
|
||||||
|
"time_start" : "$META_DATE_START",
|
||||||
|
"time_stop" : "$META_DATE_STOP",
|
||||||
|
"result" : "$RET"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
mkdir -p $ARTEFACT_DIR
|
||||||
|
mv -T $BUILD_BASE/$MICOBU_TEMP_NAME $ARTEFACT_DIR
|
||||||
|
echo "Stored artefacts in $ARTEFACT_DIR"
|
||||||
|
|
||||||
|
find $ARTEFACT_BASE
|
||||||
|
cat $ARTEFACT_DIR/meta.json
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
if [ $RET -eq 0 ]; then
|
||||||
|
echo 'BUILD SUCCEEDED!'
|
||||||
|
else
|
||||||
|
echo "BUILD FAILED (exit code $RET)!"
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function build_s()
|
||||||
|
{
|
||||||
|
if [ "$1" != "$API_KEY" ]; then
|
||||||
|
echo "error: invalid key"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
shift
|
||||||
|
echo Starting build of $@
|
||||||
|
build $@
|
||||||
|
}
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
[ ! -p "$SERVER_PIPE" ] && mkfifo "$SERVER_PIPE"
|
||||||
|
while true; do
|
||||||
|
args=`cat $SERVER_PIPE | sed -n 's/\([a-zA-Z0-9]\+\) \([a-zA-Z0-9]\+\) \([a-zA-Z0-9:\-_\/\.]\+\) \([a-z0-9]\+\)/\1 \2 \3 \4/p'`
|
||||||
|
build_s $args
|
||||||
|
done
|
Loading…
Reference in New Issue
Block a user