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