summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xbackend/backend.php5
-rw-r--r--backend/functions/build.php29
-rw-r--r--backend/functions/execution.php60
-rw-r--r--frontend/css/build.css40
-rw-r--r--frontend/css/task.css29
-rw-r--r--frontend/functions/display_time.php35
-rw-r--r--frontend/include/footer.php2
-rw-r--r--frontend/pages/downloadimage.php32
-rw-r--r--frontend/pages/logview.php35
-rw-r--r--frontend/pages/passthrough.php8
-rw-r--r--frontend/pages/welcome.php11
-rw-r--r--frontend/routing.csv3
-rw-r--r--shared/classes/0sql_row_obj.php6
-rw-r--r--shared/classes/build.php51
-rw-r--r--shared/classes/profile.php32
-rw-r--r--shared/classes/task.php93
-rw-r--r--shared/functions/throw_exception.php (renamed from frontend/functions/throw_exception.php)0
-rw-r--r--todo10
18 files changed, 360 insertions, 121 deletions
diff --git a/backend/backend.php b/backend/backend.php
index d6e4cb4..7f8d5f0 100755
--- a/backend/backend.php
+++ b/backend/backend.php
@@ -22,6 +22,7 @@ while (true) {
if ($r->rowCount()) {
$build=new sql_build($r->fetch(PDO::FETCH_ASSOC));
$build->start=time();
+ $build->status='build/running';
$build->write();
echo 'Starting build id='.$build->id."\n";
$image=null;
@@ -29,13 +30,15 @@ while (true) {
$image=build($build);
} catch (Exception $e) {
echo 'Caught exception: '.$e->getMessage()."\n";
+ $build->status='finished/failed: '.$e->getMessage();
}
$build->finish=time();
- $build->write();
echo 'Finished with build id='.$build->id."\n";
if (isset($image)) {
echo "Completed image at $image\n";
+ $build->status='finished/success';
}
+ $build->write();
}
echo 'Sleeping...';
sleep(5);
diff --git a/backend/functions/build.php b/backend/functions/build.php
index df3fea5..8f1bda8 100644
--- a/backend/functions/build.php
+++ b/backend/functions/build.php
@@ -9,7 +9,6 @@ function build(&$build) {
$makeconf['pkgdir']=$conf['pkgdir_root'].'/'.$profile->pkgdir;
$makeconf['chost']=$headers['chost'];
$makeconf['accept_keywords']=$headers['accept_keywords'];
- $build->status='build/started';
$build->write();
$W=WORK.'/build-'.$build->id;
fatal(log_status('Creating work directory '.$W, mkdir($W, 0700)));
@@ -26,8 +25,8 @@ function build(&$build) {
$makeconf['portage_tmpdir']=$W.'/tmp';
$makeconf['emerge_default_opts']=$conf['emerge_default_opts'];
$contents='';
- foreach ($makeconf as $name => $val) { // TODO maybe shell_escape $val
- $contents.=strtoupper($name).'="'.$val.'"'."\n";
+ foreach ($makeconf as $name => $val) {
+ $contents.=strtoupper($name).'='.escapeshellarg($val)."\n";
}
unset($makeconf);
fatal(log_status('Writing '.$C.'/make.conf', file_put_contents($C.'/etc/make.conf', $contents)));
@@ -37,16 +36,28 @@ function build(&$build) {
'PORTAGE_CONFIGROOT' => $C,
'PATH' => $_ENV['PATH']
);
- fatal(log_command($build, 'emerge --info', null, $env) ==0 );
- fatal(log_command($build, 'emerge -p system', null, $env) ==0 );
- fatal(log_command($build, 'emerge system', null, $env) ==0 );
+ sql_task::execute_task('emerge --info', $build, true, null, $env);
+// fatal(log_command($build, 'emerge --info', null, $env) == 0);
+ sql_task::execute_task('emerge -p system', $build, true, null, $env);
+// fatal(log_command($build, 'emerge -p system', null, $env) == 0);
+ sql_task::execute_task('emerge system', $build, true, null, $env);
+// fatal(log_command($build, 'emerge system', null, $env) == 0);
if (isset($opts['install_packages'])) {
- fatal(log_command($build, 'emerge -p '.$opts['install_packages'], null, $env) == 0);
+ $pkgs=array();
foreach (explode(' ', $opts['install_packages']) as $atom) {
- fatal(log_command($build, 'emerge '.$atom, null, $env) == 0);
+ $pkgs[]=escapeshellarg($atom);
+ }
+ sql_task::execute_task('emerge -p '.implode(' ', $pkgs), $build, true, null, $env);
+// fatal(log_command($build, 'emerge -p '.implode(' ', $pkgs), null, $env) == 0);
+ foreach ($pkgs as $atom) {
+ sql_task::execute_task('emerge '.$atom, $build, true, null, $env);
+// fatal(log_command($build, 'emerge '.$atom, null, $env) == 0);
}
}
- fatal(log_command($build, "tar -p --same-owner -czvf '$W/image.tar.gz' -C '$W/image' .") == 0);
+ sql_task::execute_task("tar -p --same-owner -czvf '$W/image.tar.gz' -C '$W/image' .", $build, true, null, $env);
+// fatal(log_command($build, "tar -p --same-owner -czvf '$W/image.tar.gz' -C '$W/image' .") == 0);
+ chmod($W, 0755);
+ chmod("$W/image.tar.gz", 0644);
return "$W/image.tar.gz";
}
?>
diff --git a/backend/functions/execution.php b/backend/functions/execution.php
deleted file mode 100644
index 73cef31..0000000
--- a/backend/functions/execution.php
+++ /dev/null
@@ -1,60 +0,0 @@
-<?php
-// TODO this should be part of the task class
-function log_command(&$build, $command, $path=null, $env=null) {
- log_msg("Executing $command... ", false);
- $descriptorspec=array(
- 0 => array('pipe', 'r'), // STDIN
- 1 => array('pipe', 'w'), // STDOUT
- 2 => array('pipe', 'w') // STDERR
- );
- $task=new sql_task(null, $build->id, $command, null);
- $task->start=time();
- $task->write();
- $p=proc_open($command, $descriptorspec, $pipes, $path, $env);
- foreach ($pipes as $pipe) {
- stream_set_blocking($pipe, 0);
- }
- $msg=0;
- while (true) {
- $status=proc_get_status($p);
- // We have to set these all to variables because stream_select requires pass by reference
- $null=null;
- $outs=array($pipes[1], $pipes[2]);
- $s=stream_select($outs, $null, $null, 1);
- if ($s) {
- $c=stream_get_contents($pipes[2]);
- if ($c) {
- // STDERR
- $entry=new sql_buildlog_entry($task->id, $msg++, time(), 'stderr', $c);
- $entry->write();
- }
- $c=stream_get_contents($pipes[1]);
- if ($c) {
- // STDOUT
- $entry=new sql_buildlog_entry($task->id, $msg++, time(), 'stdout', $c);
- $entry->write();
- }
- }
- if ($status['running'] === false) {
- $exit_status=$status['exitcode'];
- break;
- }
- }
- $task->finish=time();
- foreach ($pipes as $pipe) {
- fclose($pipe);
- }
- if (!isset($exit_status)) {
- $exit_status=proc_close($p);
- }
- $task->exit=$exit_status;
- $task->write();
- if ($exit_status == 0) {
- log_msg(color('[success]', 'green'));
- } else {
- log_msg(color('[exit code '.$exit_status.']', 'red'));
- }
- // Handle end status
- return $exit_status;
-}
-?>
diff --git a/frontend/css/build.css b/frontend/css/build.css
new file mode 100644
index 0000000..7ae7345
--- /dev/null
+++ b/frontend/css/build.css
@@ -0,0 +1,40 @@
+div.build {
+ margin: 1em;
+ border: 1px dotted green;
+ padding: 1em;
+}
+div.build span.name {
+ font-size: 120%;
+}
+div.build span.status {
+ font-size: 95%;
+}
+div.build span.status.failed {
+ color: red;
+}
+div.build span.status.successful {
+ color: green;
+}
+div.build span.status.building {
+ background-color: black;
+ color: yellow;
+}
+div.build span.status.queued {
+ background-color: yellow;
+}
+div.build span.status.config {
+ color: teal;
+}
+div.build span.links {
+ font-size: 90%;
+}
+div.build span.links a:visited {
+ color: blue;
+}
+div.build div.time {
+ font-size: 90%;
+}
+div.build div.time span.time {
+ font-family: monospace;
+ font-weight: bold;
+}
diff --git a/frontend/css/task.css b/frontend/css/task.css
new file mode 100644
index 0000000..d3e9892
--- /dev/null
+++ b/frontend/css/task.css
@@ -0,0 +1,29 @@
+div.task {
+ font-family: monospace;
+}
+div.task span.command {
+ font-size: 110%;
+ font-weight: bold;
+}
+div.task span.status {
+ font-weight: bold;
+}
+div.task span.status.successful {
+ color: green;
+}
+div.task span.status.failed {
+ color: red;
+}
+div.task span.status.running {
+ color: yellow;
+ background-color: black;
+}
+div.task span.status.queued {
+ background-color: yellow
+}
+div.task span.time span.time {
+ font-weight: bold;
+}
+div.task a {
+ color: blue;
+}
diff --git a/frontend/functions/display_time.php b/frontend/functions/display_time.php
new file mode 100644
index 0000000..8c3cbce
--- /dev/null
+++ b/frontend/functions/display_time.php
@@ -0,0 +1,35 @@
+<?php
+// Returns a nice two-piece human-friendly length of time from the given number of seconds
+function display_time($n) {
+ $s=$n%60;
+ $n-=$s;
+ $n/=60;
+ $m=$n%60;
+ $n-=$m;
+ $n/=60;
+ $h=$n%24;
+ $n-=$h;
+ $n/=24;
+ $d=$n%365;
+ $n-=$d;
+ $n/=365;
+ $y=$n;
+ $array=array(
+ 'y' => $y,
+ 'd' => $d,
+ 'h' => $h,
+ 'm' => $m,
+ 's' => $s
+ );
+ $r=array();
+ foreach ($array as $label => $val) {
+ if ($val > 0) {
+ $r[]="$val $label";
+ if (count($r) == 2) {
+ break;
+ }
+ }
+ }
+ return $r?implode(' ', $r):'0 s';
+}
+?>
diff --git a/frontend/include/footer.php b/frontend/include/footer.php
index 1f2f241..96ba9b9 100644
--- a/frontend/include/footer.php
+++ b/frontend/include/footer.php
@@ -5,7 +5,7 @@ if (isset($S['start'])) {
$diff=round(microtime(true)-$S['start'], 3);
echo 'Execution took '.$diff.' seconds.<br/>'."\n";
}
-echo '&#169; Eitan Mosenkis '.date('Y').'</div>';
+echo /*'&#169; Eitan Mosenkis '.date('Y').*/'</div>';
if ($conf['debug']) {
echo '<br/><div id="debug"><div class="heading" onclick="toggledebugbox()">Debug (<span id="debugcount">'.$S['debugrow'].'</span>) <span id="debugactions">[<a href="javascript:cleardebug()" id="debugclear">Clear</a>] [<a href="javascript:closedebug()" id="debugclose">X</a>]</span></div><div id="debugbox">'./*$state->debug.*/'</div></div>'."\n";
echo '<script type="text/javascript">
diff --git a/frontend/pages/downloadimage.php b/frontend/pages/downloadimage.php
new file mode 100644
index 0000000..b3632e3
--- /dev/null
+++ b/frontend/pages/downloadimage.php
@@ -0,0 +1,32 @@
+<?php
+function init_downloadimage() {
+ global $S, $request;
+ if (!isset($S['user'])) {
+ return 'login';
+ }
+ if (!isset($request['build']) || !preg_match('/^[a-zA-Z0-9]{6}$/', $request['build'])) {
+ debug('downlaodimage', 'No build or badly formatted build requested');
+ return '404';
+ }
+ $r=$S['pdo']->query('SELECT * FROM `builds` WHERE `owner`='.$S['user']->id.' AND `id`="'.$request['build'].'"');
+ if ($r->rowCount() == 0) {
+ debug('downloadimage', 'build not found or not owned by user');
+ return '404';
+ }
+ $build=new sql_build($r->fetch(PDO::FETCH_ASSOC));
+ $S['file']=WORK.'/build-'.$build->id.'/image.tar.gz';
+ if (!is_file($S['file'])) {
+ debug('downloadimage', 'image file '.$S['file'].' not found');
+ return '404';
+ }
+ contenttype('application/x-gzip');
+ header('Content-Length: '.filesize($S['file']));
+ header('Content-Description: File Transfer');
+ header('Content-Transfer-Encoding: binary');
+ header('Content-Disposition: attachment; filename="ingenue-'.$build->id.'.tar.gz"');
+}
+function body_downloadimage() {
+ global $S;
+ readfile($S['file']);
+}
+?>
diff --git a/frontend/pages/logview.php b/frontend/pages/logview.php
index 90a1fc9..2884c48 100644
--- a/frontend/pages/logview.php
+++ b/frontend/pages/logview.php
@@ -13,7 +13,9 @@ function body_logview() {
return;
}
$task=new sql_task($r->fetch(PDO::FETCH_ASSOC));
- echo '<h3>Task '.$task->id.': '.$task->command.' ';
+ echo '<div style="font-size: 130%">'.$task->display().'</div>';
+ echo '<a href="'.url('logs/build'.$task->build).'">Back</a><br/>';
+/* echo '<h3>Task '.$task->id.': '.$task->command.' ';
if (isset($task->exit)) {
if ($task->exit == 0) {
echo '<span style="color: green">[completed]</span>';
@@ -23,7 +25,7 @@ function body_logview() {
} else {
echo '<span style="color: yellow">[running]</span>';
}
- echo '</h3>';
+ echo '</h3>';*/
$page=isset($request['page']) && is_numeric($request['page'])?$request['page']:1;
$count=$S['pdo']->query('SELECT COUNT(*) FROM `buildlogs` WHERE `task`='.$task->id)->fetch(PDO::FETCH_COLUMN);
$pager='';
@@ -54,6 +56,7 @@ function body_logview() {
}
echo '</div>';
echo $pager;
+ echo '<a href="'.url('logs/build'.$task->build).'">Back</a><br/>';
} else {
if ($count) {
echo print_error("There aren't $page pages. Try an <a href=\"".url('logs/task'.$task->id)."\">earlier page</a>.");
@@ -62,7 +65,8 @@ function body_logview() {
}
}
} elseif (isset($request['build']) && preg_match('/[a-z0-9]{6}/', $request['build'])) {
- echo '<h3>Build '.$request['build'].'</h3>';
+ $build=new sql_build($request['build']);
+ echo $build->display();
$r=$S['pdo']->query('SELECT * FROM `tasks` WHERE `build`="'.$request['build'].'" ORDER BY `id` ASC');
if ($r->rowCount() == 0) {
echo '<b>No tasks found.</b>';
@@ -70,23 +74,27 @@ function body_logview() {
$i=0;
while ($task=$r->fetch(PDO::FETCH_ASSOC)) {
$task=new sql_task($task);
- echo '<a href="'.url('logs/task'.$task->id).'">Task #'.++$i.'</a>: '.htmlentities($task->command).'<br/>';
+ echo $task->display();
+// echo '<a href="'.url('logs/task'.$task->id).'">Task #'.++$i.'</a>: '.htmlentities($task->command).'<br/>';
}
} else {
- $r=$S['pdo']->query('SELECT * FROM `builds` ORDER BY `ctime` ASC');
+ $r=$S['pdo']->query('SELECT * FROM `builds` ORDER BY `ctime` IS NULL ASC, `ctime` ASC, `status` DESC');
if ($r->rowCount() == 0) {
echo print_warning('No builds found.');
}
while ($build=$r->fetch(PDO::FETCH_ASSOC)) {
$build=new sql_build($build);
- echo '<a href="'.url('logs/build'.$build->id).'">Build '.$build->id.'</a>: ';
- if (isset($build->name)) {
- echo htmlentities($build->name);
- }
- echo '<br/>';
+ echo $build->display();
}
}
}
+function process_ansi($txt) {
+ $txt=str_replace("\x1b[K", '', nl2br($txt));
+ do {
+ $txt=preg_replace('#.\ch#', '', $txt, -1, $count); // ^H = Backspace
+ } while ($count);
+ return preg_replace_callback('#\e\[(?:(?:([0-9]{1,2});)*([0-9]{1,2}))?m#', 'ansi_callback', $txt);
+}
function ansi_callback($match) {
$a=explode(' ', 'bright dim underscore blink reverse hidden');
$c=explode(' ', 'black red green yellow blue magenta cyan white');
@@ -117,11 +125,4 @@ function ansi_callback($match) {
}
return $r;
}
-function process_ansi($txt) {
- $txt=str_replace("\x1b[K", '', nl2br($txt));
- do {
- $txt=preg_replace('#.\ch#', '', $txt, -1, $count); // ^H = Backspace
- } while ($count);
- return preg_replace_callback('#\e\[(?:(?:([0-9]{1,2});)*([0-9]{1,2}))?m#', 'ansi_callback', $txt);
-}
?>
diff --git a/frontend/pages/passthrough.php b/frontend/pages/passthrough.php
index b8f0268..fd989f1 100644
--- a/frontend/pages/passthrough.php
+++ b/frontend/pages/passthrough.php
@@ -34,6 +34,14 @@ function init_passthrough() {
case 'php':
$S['notemplates']=true;
break;
+ case 'gz':
+ case 'gzip':
+ contenttype('application/x-gzip');
+ break;
+ case 'tbz2':
+ case 'bz2':
+ contenttype('application/bzip2');
+ break;
default:
debug('passthrough', 'Unknown extension '.$request['ext']);
return '404';
diff --git a/frontend/pages/welcome.php b/frontend/pages/welcome.php
index 3c7da87..d326f08 100644
--- a/frontend/pages/welcome.php
+++ b/frontend/pages/welcome.php
@@ -3,19 +3,8 @@ function init_welcome() {
global $S;
$S['title']='Welcome';
}
-function print_query($q) {
- echo '<pre>'.$q.'</pre>';
- global $S;
- $S['pdo']->query($q);
-}
function body_welcome() {
global $S;
echo '<h2>Welcome</h2>';
- $e=new sql_buildlog_entry();
-// print_query($e->drop_table());
-// print_query($e->create_table());
- $e=new sql_task();
-// print_query($e->drop_table());
-// print_query($e->create_table());
}
?>
diff --git a/frontend/routing.csv b/frontend/routing.csv
index 3d21751..841529d 100644
--- a/frontend/routing.csv
+++ b/frontend/routing.csv
@@ -14,12 +14,15 @@
# Logs
^logs$ logview
^logs/build([a-z0-9]{6})$ logview build
+^logs/build([a-z0-9]{6})/live$ livelog build
^logs/task([0-9]+)$ logview task
^logs/task([0-9]+)/([0-9]+)$ logview task page
# Build creation
^create$ wizard
^create/([a-zA-Z0-9]{6})$ wizard build
^create/([a-zA-Z0-9]{6})/([0-9]+)$ wizard build step
+# Download finished image
+^download/([a-zA-Z0-9]{6})$ downloadimage build
# Session
^login$ login
^logout$ logout
diff --git a/shared/classes/0sql_row_obj.php b/shared/classes/0sql_row_obj.php
index 0d75d37..a31403c 100644
--- a/shared/classes/0sql_row_obj.php
+++ b/shared/classes/0sql_row_obj.php
@@ -283,7 +283,7 @@ abstract class sql_row_obj { // If the name of this class changes, it must be up
if (is_array($sql_val)) {
//echo $name."\n";
print_r($sql_val);
- throw new Exception('Failed to write '.get_class($this).' object to table `'.$this->table.'` because column '.$name.' ('.$sql_val['description'].') '.$sql_val['reason'].' (value: '.$sql_val['value'].')');
+ throw new Exception('Failed to write '.get_class($this).' object to table `'.$this->table.'` because column `'.$name.'` ('.$sql_val['description'].') '.$sql_val['reason'].' (value: '.$sql_val['value'].')');
}
if (++$i > 1) {
$q.=', ';
@@ -550,6 +550,8 @@ class sql_col {
}
if (isset($array['not null'])) {
$this->not_null=$array['not null']?true:false;
+ } elseif (isset($array['not_null'])) {
+ $this->not_null=$array['not_null']?true:false;
} elseif (isset($array['null'])) {
$this->not_null=$array['null']?false:true;
}
@@ -793,6 +795,8 @@ class sql_col {
} else {
if ($this->has_length() && strlen($value) > $this->length) {
// TODO warning here about length being over the maximum
+ // Needs to have a different way of handling things that
+ // aren't actually lengths, like ENUM
}
if ($this->is_numeric()) {
if (!is_numeric($value)) {
diff --git a/shared/classes/build.php b/shared/classes/build.php
index a2c2a0f..b290198 100644
--- a/shared/classes/build.php
+++ b/shared/classes/build.php
@@ -5,6 +5,7 @@ class sql_build extends sql_row_obj {
'type' => 'CHAR',
'length' => 6,
'not_null' => true,
+ 'unique' => true
),
'owner' => array (
'type' => 'INT',
@@ -70,5 +71,55 @@ class sql_build extends sql_row_obj {
}
return $opts;
}
+ // Returns HTML code describing this build's status (for human consumption)
+ function display() {
+ global $S;
+ $format='D j M Y G:i:s';
+ $html='<div class="build"><span class="name">'.(isset($this->name) && strlen($this->name)?htmlentities($this->name):'Unnamed Build').'</span> ';
+ $status=explode('/', $this->status, 2);
+ if ($status[0] == 'config') {
+ $status[1]=substr($status[1], strpos($status[1], 'p')+1);
+ $html.='<span class="status config">[Configuration step '.$status[1].']</span><br/><span class="links"><a href="'.url('create/'.$this->id).'">Continue configuring</a></span>';
+ } elseif ($status[0] == 'build') {
+ if ($status[1] == 'ready') {
+ $total=$S['pdo']->query('SELECT COUNT(*) FROM `builds` WHERE `status`="build/ready"')->fetch(PDO::FETCH_COLUMN);
+ $num=$S['pdo']->query('SELECT COUNT(*) FROM `builds` WHERE `status`="build/ready" AND `ctime` <= '.$this->ctime)->fetch(PDO::FETCH_COLUMN);
+ $html.="<span class=\"status queued\">[Queued ($num/$total)]</span>";
+ } elseif ($status[1]='running') {
+ // Add link to regular log viewer?
+ // Build stage X
+ $html.='<span class="status building">[building]</span><br/><span class="links"><a href="'.url('logs/build'.$this->id.'/live').'">Watch</a></span>';
+ } else {
+ throw_exception('Unrecognized build status '.$this->status);
+ }
+ } elseif ($status[0] == 'finished') {
+ $status=explode(': ', $status[1], 2);
+ if ($status[0] == 'success') {
+ $html.='<span class="status successful">[successful]</span><br/><span class="links"><a href="'.url('download/'.$this->id).'">Download image</a> &bull; <a href="'.url('logs/build'.$this->id).'">Build log</a></span>';
+ } elseif ($status[0] == 'failed') {
+ $html.='<span class="status failed">[failed: '.htmlentities($status[1]).']</span><br/><span class="links"><a href="'.url('logs/build'.$this->id.'/failure').'">View output of failed command</a> &bull; <a href="'.url('logs/build'.$this->id).'">Build log</a></span>';
+ } else {
+ throw_exception('Unrecognized build status '.$this->status);
+ }
+ } else {
+ throw_exception('Unrecognized build status '.$this->status);
+ }
+ if (isset($this->ctime)) {
+ $html.='<div class="time">Submitted for build at: <span class="time">'.date($format, $this->ctime).' UTC</span><br/>';
+ if (isset($this->start)) {
+ $html.='Build started at: <span class="time">'.date($format, $this->start).' UTC</span><br/>';
+ if (isset($this->finish)) {
+ $html.='Build finished at: <span class="time">'.date($format, $this->finish).' UTC</span><br/>Total build time: <span class="time">'.display_time($this->finish-$this->start).'</span>';
+ } else {
+ $html.='Running for: <span class="time">'.display_time(time()-$this->start).'</span>';
+ }
+ } else {
+ $html.='Queued for: <span class="time">'.display_time(time()-$this->ctime).'</span>';
+ }
+ $html.='</div>';
+ }
+ $html.='</div>';
+ return $html;
+ }
}
?>
diff --git a/shared/classes/profile.php b/shared/classes/profile.php
index 4f89f4f..f45106b 100644
--- a/shared/classes/profile.php
+++ b/shared/classes/profile.php
@@ -9,13 +9,11 @@ class sql_profile extends sql_row_obj {
'name' => array (
'type' => 'VARCHAR',
'length' => 255,
- 'not_null' => true,
'unique' => true
),
'order' => array (
'type' => 'TINYINT',
- 'length' => 4,
- 'not_null' => true
+ 'length' => 4
),
'flags' => array (
'type' => 'VARCHAR',
@@ -24,12 +22,9 @@ class sql_profile extends sql_row_obj {
)
);
- private function open_Packages() {
- global $conf;
- return fopen($conf['pkgdir_root'].'/'.$this->pkgdir.'/Packages', 'r');
- }
public function get_headers($return_filehandle=false) {
- $file=$this->open_Packages();
+ global $conf;
+ $file=fopen($conf['pkgdir_root'].'/'.$this->pkgdir.'/Packages', 'r');
$headers=array();
while (!feof($file)) {
$line=rtrim(fgets($file));
@@ -51,7 +46,6 @@ class sql_profile extends sql_row_obj {
}
public function &get_packages() {
list($headers, $file)=$this->get_headers(true);
- // echo $headers['accept_keywords'];
$accept_keywords=explode(' ', $headers['accept_keywords']);
$p=array();
$cur=null;
@@ -71,10 +65,16 @@ class sql_profile extends sql_row_obj {
continue;
}
if (isset($p[$cat][$name][$ver])) {
- $dups[$cat][$name][$ver]=$p[$cat][$name][$ver];
- if (defined('STDERR')) {
- debug($this->pkgdir.": Duplicate package $cat/$name-$ver");
- }
+ debug("Duplicate package $cat/$name-$ver");
+ // We don't really care about having correct info
+/* global $conf;
+ $tbz=$conf['pkgdir_root'].'/'.$this->pkgdir.'/'.(isset($p[$cat][$name][$ver]['path'])?$p[$cat][$name][$ver]['path']:"$cat/$name-$ver.tbz2");
+ if (filesize($tbz) == $p[$cat][$name][$ver]['size']) {
+ debug($this->pkgdir.": Duplicate package $cat/$name-$ver - current entry matches size, dropping new entry");
+ continue;
+ } else {
+ debug($this->pkgdir.": Duplicate package $cat/$name-$ver - current entry invalid, using new entry");
+ }*/
}
$p[$cat][$name][$ver]=array();
$cur=&$p[$cat][$name][$ver];
@@ -87,6 +87,12 @@ class sql_profile extends sql_row_obj {
case 'DESC':
$cur['description']=$val;
break;
+/* case 'SIZE':
+ $cur['size']=$val;
+ break;
+ case 'PATH':
+ $cur['path']=$val;
+ break;*/
default:
$cur[$name]=$val;
}
diff --git a/shared/classes/task.php b/shared/classes/task.php
index 19afff7..7a02719 100644
--- a/shared/classes/task.php
+++ b/shared/classes/task.php
@@ -12,19 +12,17 @@ class sql_task extends sql_row_obj {
'type' => 'CHAR',
'length' => 6,
'not_null' => true,
- 'default' => ''
+ 'default' => '',
+ 'refers to' => 'builds.id'
),
'command' => array (
- 'type' => 'VARCHAR',
- 'length' => 255,
- 'not_null' => true,
- 'default' => ''
+ 'type' => 'TEXT',
+ 'not_null' => true
),
'start' => array (
'type' => 'INT',
'length' => 10,
- 'unsigned' => true,
- 'not_null' => true
+ 'unsigned' => true
),
'finish' => array (
'type' => 'INT',
@@ -38,5 +36,86 @@ class sql_task extends sql_row_obj {
)
);
+ function display() {
+ $html='<div class="task">[<a href="'.url('logs/task'.$this->id).'">log</a>] <span class="command">'.htmlentities($this->command).'</span> ';
+ if (isset($this->start)) {
+ if (isset($this->finish)) {
+ $html.='<span class="status '.($this->exit==0?'successful">[successful]':'failed">[exit status '.$this->exit.']').'</span> <span class="time">Finished in <span class="time">'.display_time($this->finish-$this->start).'</span></span>';
+ } else {
+ $html.='<span class="status running">[running]</span> <span class="time">Running for <span class="time">'.display_time(time()-$this->start).'</span></span>';
+ }
+ } else {
+ $total=$S['pdo']->query('SELECT COUNT(*) FROM `tasks` WHERE `build`="'.$this->build.'" AND `start` IS NULL')->fetch(PDO::FETCH_COLUMN);
+ $num=$S['pdo']->query('SELECT COUNT(*) FROM `tasks` WHERE `builds`="'.$this->build.'" AND `start` IS NULL AND `id` <= '.$this->id);
+ $html.="<span class=\"status queued\">[queued $num/$total]</span>";
+ }
+ $html.='</div>';
+ return $html;
+ }
+ function execute($fatal=true, $path=null, $env=null) {
+ if (!isset($this->command, $this->build)) {
+ throw_exception('$this->command or $this->build not set');
+ } elseif (isset($this->start)) {
+ throw_exception('task has already executed: start is '.$this->start);
+ }
+ log_msg('Executing '.$this->command.'...', false);
+ $descriptorspec=array(
+ 0 => array('pipe', 'r'), // STDIN
+ 1 => array('pipe', 'w'), // STDOUT
+ 2 => array('pipe', 'w') // STDERR
+ );
+ $this->start=time();
+ $this->write();
+ $p=proc_open($this->command, $descriptorspec, $pipes, $path, $env);
+ foreach ($pipes as $pipe) {
+ stream_set_blocking($pipe, 0);
+ }
+ $msg=0;
+ while (true) {
+ $status=proc_get_status($p);
+ $null=null; // We have to set these all to variables because stream_select requires pass by reference
+ $outs=array($pipes[1], $pipes[2]);
+ $s=stream_select($outs, $null, $null, 1);
+ if ($s) {
+ $c=stream_get_contents($pipes[2]);
+ if ($c) {
+ // STDERR
+ $entry=new sql_buildlog_entry($this->id, $msg++, time(), 'stderr', $c);
+ $entry->write();
+ }
+ $c=stream_get_contents($pipes[1]);
+ if ($c) {
+ // STDOUT
+ $entry=new sql_buildlog_entry($this->id, $msg++, time(), 'stdout', $c);
+ $entry->write();
+ }
+ }
+ if ($status['running'] === false) {
+ $this->exit=$status['exitcode'];
+ break;
+ }
+ }
+ $this->finish=time();
+ foreach ($pipes as $pipe) {
+ fclose($pipe);
+ }
+ if (!isset($this->exit)) {
+ $this->exit=proc_close($p);
+ }
+ $this->write();
+ if ($this->exit == 0) {
+ log_msg(color('[success]', 'green'));
+ } else {
+ log_msg(color('[exit code '.$this->exit.']', 'red'));
+ if ($fatal) {
+ throw_exception($this->command.' returned with exit status '.$this->exit);
+ }
+ }
+ return $this->exit;
+ }
+ static function execute_task($command, $build, $fatal=true, $path=null, $env=null) {
+ $task=new sql_task(null, is_object($build)?$build->id:$build, $command);
+ return $task->execute($fatal, $path, $env);
+ }
}
?>
diff --git a/frontend/functions/throw_exception.php b/shared/functions/throw_exception.php
index 6a9c288..6a9c288 100644
--- a/frontend/functions/throw_exception.php
+++ b/shared/functions/throw_exception.php
diff --git a/todo b/todo
index 8e85496..7e0febf 100644
--- a/todo
+++ b/todo
@@ -5,6 +5,14 @@ Move more functions into corresponding classes
Give profiles an auto_increment ID so they can have their directories moved without breaking the db
Have backend handle builds that it finds to already be running (break in to steps and store current status)
Add a PID file so backend can't start twice
-Make frontend package adding a little more user-friendly
+Make frontend package adding a little more user-friendly (ajax or client-side js search)
Restructure the backend for modularity
Write an init script and a live git ebuild
+Write an AJAX-based self-updating status viewer
+Add logging besides just commands (restructure the builglogs table)
+Make backend do a dummy run through and queue all commands and other tasks, then execute them (for better status handling, easier debugging, etc.)
+Have builds and tasks not give links to logs if we're already viewing the logs
+Either make task status a TEXT or stop putting command name in the status (via thrown exception) - we can fetch this later anyway - just store the task id that failed
+Move finished images somewhere else and rm -r the work directory when finished instead of chmodding stuff
+Remove the auto_increment on tasks and change it to just increment itself based on build
+Make output coloring HTML-safe, add css for everything and use classes to reduce code length, add back in metadata to logs