xaizek / rocketgit (License: AGPLv3+) (since 2018-12-09)
Light and fast Git hosting solution suitable to serve both as a hub or as a personal code storage with its tickets, pull requests, API and much more.
<root> / inc / builder.inc.php (a511694aebf333b5cc41e8043d771c40ae6e58b4) (7,968B) (mode 100644) [raw]
<?php
$INC = isset($INC) ? $INC : dirname(__FILE__);
require_once($INC . '/repo.inc.php');
require_once($INC . '/user.inc.php');

/*
 * Function to load job list
 */
function rg_builder_load_jobs($db, $where, $order, $limit)
{
	$ret = array();
	$ret['ok'] = 0;
	$ret['list'] = array();

	while (1) {
		$sql = 'SELECT * FROM build_jobs'
			. ' WHERE ' . $where
			. ' ORDER BY ' . $order
			. ' ' . $limit;
		$res = rg_sql_query($db, $sql);
		if ($res === FALSE)
			break;
		while (($row = rg_sql_fetch_array($res))) {
			$jid = $row['id'];

			// TODO: set 'url' when adding the job in queue!
			// Why not now?

			// TODO: in case of errors, we should abort the job!
			$row['request'] = rg_unserialize($row['request']);
			if ($row['request'] === FALSE)
				$row['request'] = array();
			$row['status'] = rg_unserialize($row['status']);
			if ($row['status'] === FALSE)
				$row['status'] = array();

			$ret['list'][$jid] = $row;
		}
		rg_sql_free_result($res);

		$ret['ok'] = 1;
		break;
	}

	return $ret;
}

/*
 * Add a build job in queue
 */
function rg_builder_add($db, $repo_id, $d)
{
	rg_log_ml('builder_add: ' . print_r($d, TRUE));

	$ret = array('ok' => 0);
	while (1) {
		$params = array(
			'repo_id' => $repo_id,
			'prio' => 10,		// TODO
			'itime' => time(),
			'request' => rg_serialize($d),
			'done' => 0,
			'status' => ''
		);
		$sql = 'INSERT INTO build_jobs (repo_id, prio, itime'
			. ', request, done, status)'
			. ' VALUES (@@repo_id@@, @@prio@@, @@itime@@'
			. ', @@request@@, @@done@@, @@status@@)';
		$res = rg_sql_query_params($db, $sql, $params);
		if ($res === FALSE) {
			$ret['errmsg'] = 'cannot add job';
			break;
		}
		rg_sql_free_result($res);

		// TODO: notify build system to not poll?
		$ret['ok'] = 1;
		break;
	}

	return $ret;
}

/*
 * List virtual machines
 * TODO: for now, only libvirt
 */
function rg_builder_vm_list()
{
	$cmd = 'virsh list --name';
	$r = rg_exec($cmd, '', FALSE, FALSE, FALSE);
	if ($r['ok'] != 1) {
		rg_log('Cannot find out virtual machines: ' . $r['errmsg']);
		return FALSE;
	}

	return explode("\n", trim($r['data']));
}

/*
 * Function executed when a job is done
 */
function rg_builder_done($db, $job, $s)
{
	rg_log_enter('builder_done');
	//rg_log_ml('DEBUG: builder_done: job: ' . print_r($job, TRUE));
	//rg_log_ml('DEBUG: builder_done: status: ' . print_r($s, TRUE));

	// old way
	$req = isset($job['request']) ? $job['request'] : $job;

	$job['done'] = time();

	$ret = FALSE;
	$rollback = FALSE;
	while (1) {
		$res = rg_sql_begin($db);
		if ($res === FALSE)
			break;

		$rollback = TRUE;

		$labels = $s['labels'];

		// Some cosmetic stuff
		$env = $req['env'];
		$labels[] = 'worker_elap/' . ($s['done'] - $s['start']) . 's';
		$labels[] = 'wait_time/' . ($job['worker_sent'] - $job['itime']) . 's';
		$labels[] = 'date/' . gmdate('Y-m-d', $job['itime']);
		$labels[] = 'time/' . gmdate('H:i', $job['itime']);

		// add labels to the commit
		$params = array(
			'repo_id' => $job['repo_id'],
			'head' => $req['head'],
			'type' => 'build',
			'misc' => $env,
			'labels' => rg_serialize($labels),
			'itime' => time()
		);
		$sql = 'DELETE FROM commit_labels'
			. ' WHERE repo_id = @@repo_id@@'
			. ' AND type = @@type@@'
			. ' AND misc = @@misc@@'
			. ' AND head = @@head@@';
		$res = rg_sql_query_params($db, $sql, $params);
		if ($res === FALSE)
			break;
		rg_sql_free_result($res);

		$sql = 'INSERT INTO commit_labels (repo_id, head, type, misc'
			. ', labels, itime)'
			. ' VALUES (@@repo_id@@, @@head@@, @@type@@'
			. ', @@misc@@, @@labels@@, @@itime@@)';
		$res = rg_sql_query_params($db, $sql, $params);
		if ($res === FALSE)
			break;
		rg_sql_free_result($res);
		rg_cache_unset('repo_commit_labels' . '::' . $job['repo_id']
			. '::' . $req['head'], RG_SOCKET_NO_WAIT);

		$params = array(
			'id' => $job['id'],
			'done' => $job['done'],
			'status' => rg_serialize($s)
		);
		$sql = 'UPDATE build_jobs SET done = @@done@@'
			. ', status = @@status@@'
			. ' WHERE id = @@id@@';
		$res = rg_sql_query_params($db, $sql, $params);
		if ($res === FALSE)
			break;
		rg_sql_free_result($res);

		$ev = array(
			'category' => 'wh_build_job_done',
			'prio' => 100,
			'ui' => array('uid' => $req['uid']),
			'job' => $job,
			'status' => $s
		);
		$r = rg_event_add($db, $ev);
		if ($r !== TRUE) {
			rg_repo_set_error('cannot add event'
				. ' (' . rg_event_error() . ')');
			break;
		}

		$res = rg_sql_commit($db);
		if ($res === FALSE)
			break;

		$rollback = FALSE;

		$key = 'wh' . '::' . $req['uid'] . '::' . 'list'
			. '::' . $req['hook_id'];
		unset($params['id']);
		rg_cache_merge($key, $params, RG_SOCKET_NO_WAIT);

		rg_event_signal_daemon('', 0);

		$ret = TRUE;
		break;
	}

	if ($rollback)
		rg_sql_rollback($db);

	rg_log_exit();
	return $ret;
}

/*
 * Nice status for a job
 */
function rg_builder_nice_status(&$a)
{
	rg_log_ml('DEBUG: nice_status: a: ' . print_r($a, TRUE));
	$a['start_nice'] = gmdate('Y-m-d H:i', intval($a['start']));
	$a['net_ok_nice'] = gmdate('Y-m-d H:i', intval($a['net_ok']));
	$a['pkgs_ok_nice'] = gmdate('Y-m-d H:i', intval($a['pkgs_ok']));
	$a['done_nice'] = gmdate('Y-m-d H:i', intval($a['done']));

	if (isset($a['vm_start']) && ($a['vm_start'] > 0))
		$a['vm_start_nice'] = gmdate('Y-m-d H:i', $a['vm_start']);
	else
		$a['vm_start_nice'] = 'n/a';

	if (isset($a['build_sh_start']) && ($a['build_sh_start'] > 0))
		$a['build_sh_start_nice'] = gmdate('Y-m-d H:i', trim($a['build_sh_start']));
	else
		$a['build_sh_start_nice'] = 'n/a';

	if (!isset($a['clone_elap']))
		$a['clone_elap_nice'] = 'n/a';
	else
		$a['clone_elap_nice'] = $a['clone_elap'] . 's';

	foreach ($a['cmds'] as &$i) {
		if (empty($i['start']))
			continue;

		$i['start_nice'] = gmdate('Y-m-d H:i', intval($i['start']));
		$i['done_nice'] = gmdate('Y-m-d H:i', intval($i['done']));
		$i['elap'] = rg_human_time_interval($i['done'] - $i['start'] + 1);

		$_a = rg_xss_safe($i['log']);
		$i['HTML:log_nlbr'] = nl2br($_a);
	}
}

/*
 * Cosmetic fixes
 */
function rg_builder_cosmetic($db, &$row)
{
	rg_log_ml('DEBUG: builder_cosmetic: ' . print_r($row, TRUE));

	if (isset($row['itime']))
		$row['itime_nice'] = gmdate('Y-m-d H:i', $row['itime']);
	else
		$row['itime_nice'] = 'n/a';

	if (!isset($row['done']))
		$row['done'] = 0;
	if ($row['done'] > 0)
		$row['done_nice'] = gmdate('Y-m-d H:i', $row['done']);
	else
		$row['done_nice'] = 'n/a';

	$ri = rg_repo_info($db, $row['repo_id'], 0, '');
	if ($ri['ok'] == 1) {
		$row['ri'] = $ri;

		$row['user'] = rg_user_nice($db, $row['ri']['uid']);
	} else {
		$row['ri'] = array('name' => 'n/a');
		$row['user'] = 'n/a';
	}
}

/*
 * Lists the build jobs
 */
function rg_builder_list_high_level($db, $rg, $op, $paras)
{
	rg_prof_start('builder_list_high_level');
	rg_log_enter('builder_list_high_level op=' . $op);

	$ret = '';

	$errmsg = array();
	$rg['HTML:status'] = '';

	if ($rg['login_ui']['is_admin'] == 1)
		$where = '1 = 1';
	else
		$where = 'repo_id IN (SELECT repo_id FROM repos'
			. ' WHERE uid = ' . $rg['login_ui']['uid'] . ')';

	if (strcmp($op, 'queue') == 0)
		$where .= ' AND done = 0';

	$r = rg_builder_load_jobs($db, $where, 'itime DESC', 'LIMIT 100');
	if ($r['ok'] != 1)
		return rg_template('builder/load_err.html', $rg, TRUE /*xss*/);

	// Preparing the list for template
	$d = array();
	foreach ($r['list'] as $_id => &$i) {
		rg_builder_cosmetic($db, $i);
		if (empty($i['status'])) {
			$i['HTML:status_list'] = 'n/a';
		} else {
			if (!empty($i['status']['packages'])) {
				$_x = array();
				$_x['packages'] = rg_xss_safe($i['status']['packages']);
				$i['HTML:status_packages'] =
					rg_template('builder/packages.html', $_x, TRUE/*xss*/);
			} else {
				$i['HTML:status_packages'] = '';
			}

			rg_builder_nice_status($i['status']);
			$i['HTML:status_list'] = rg_template_table('builder/cmds',
				$i['status']['cmds'], $rg);
		}
		$d[] = $i;
	}

	rg_log_ml('DEBUG: d: ' . print_r($d, TRUE));
	return rg_template_table('builder/queue', $d, $rg);
}

Hints

Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://code.reversed.top/user/xaizek/rocketgit

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@code.reversed.top/user/xaizek/rocketgit

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a pull request:
... clone the repository ...
... make some changes and some commits ...
git push origin master