forked from lix-project/hydra
* Build the /build stuff in a separate controller.
This commit is contained in:
parent
16acb2754c
commit
cb342ed0dd
119
src/Hydra/lib/Hydra/Controller/Build.pm
Normal file
119
src/Hydra/lib/Hydra/Controller/Build.pm
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
package Hydra::Controller::Build;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use parent 'Catalyst::Controller';
|
||||||
|
use Hydra::Helper::Nix;
|
||||||
|
use Hydra::Helper::CatalystUtils;
|
||||||
|
|
||||||
|
|
||||||
|
# Security checking of filenames.
|
||||||
|
my $pathCompRE = "(?:[A-Za-z0-9-\+][A-Za-z0-9-\+\._]*)";
|
||||||
|
my $relPathRE = "(?:$pathCompRE(?:\/$pathCompRE)*)";
|
||||||
|
|
||||||
|
|
||||||
|
sub build : Chained('/') PathPart CaptureArgs(1) {
|
||||||
|
my ($self, $c, $id) = @_;
|
||||||
|
|
||||||
|
$c->stash->{id} = $id;
|
||||||
|
|
||||||
|
$c->stash->{build} = getBuild($c, $id);
|
||||||
|
|
||||||
|
if (!defined $c->stash->{build}) {
|
||||||
|
error($c, "Build with ID $id doesn't exist.");
|
||||||
|
$c->response->status(404);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$c->stash->{curProject} = $c->stash->{build}->project;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sub view_build : Chained('build') PathPart('') Args(0) {
|
||||||
|
my ($self, $c) = @_;
|
||||||
|
|
||||||
|
my $build = $c->stash->{build};
|
||||||
|
|
||||||
|
$c->stash->{template} = 'build.tt';
|
||||||
|
$c->stash->{curTime} = time;
|
||||||
|
$c->stash->{available} = isValidPath $build->outpath;
|
||||||
|
|
||||||
|
if (!$build->finished && $build->schedulingInfo->busy) {
|
||||||
|
my $logfile = $build->schedulingInfo->logfile;
|
||||||
|
$c->stash->{logtext} = `cat $logfile`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sub view_nixlog : Chained('build') PathPart('nixlog') Args(1) {
|
||||||
|
my ($self, $c, $stepnr) = @_;
|
||||||
|
|
||||||
|
my $step = $c->stash->{build}->buildsteps->find({stepnr => $stepnr});
|
||||||
|
return error($c, "Build doesn't have a build step $stepnr.") if !defined $step;
|
||||||
|
|
||||||
|
$c->stash->{template} = 'log.tt';
|
||||||
|
$c->stash->{step} = $step;
|
||||||
|
|
||||||
|
# !!! should be done in the view (as a TT plugin).
|
||||||
|
$c->stash->{logtext} = loadLog($c, $step->logfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sub view_log : Chained('build') PathPart('log') Args(0) {
|
||||||
|
my ($self, $c) = @_;
|
||||||
|
|
||||||
|
return error($c, "Build didn't produce a log.") if !defined $c->stash->{build}->resultInfo->logfile;
|
||||||
|
|
||||||
|
$c->stash->{template} = 'log.tt';
|
||||||
|
|
||||||
|
# !!! should be done in the view (as a TT plugin).
|
||||||
|
$c->stash->{logtext} = loadLog($c, $c->stash->{build}->resultInfo->logfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sub loadLog {
|
||||||
|
my ($c, $path) = @_;
|
||||||
|
|
||||||
|
die unless defined $path;
|
||||||
|
|
||||||
|
# !!! quick hack
|
||||||
|
my $pipeline = ($path =~ /.bz2$/ ? "cat $path | bzip2 -d" : "cat $path")
|
||||||
|
. " | nix-log2xml | xsltproc " . $c->path_to("xsl/mark-errors.xsl") . " -"
|
||||||
|
. " | xsltproc " . $c->path_to("xsl/log2html.xsl") . " - | tail -n +2";
|
||||||
|
|
||||||
|
return `$pipeline`;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sub download : Chained('build') PathPart('download') {
|
||||||
|
my ($self, $c, $productnr, $filename, @path) = @_;
|
||||||
|
|
||||||
|
my $product = $c->stash->{build}->buildproducts->find({productnr => $productnr});
|
||||||
|
return error($c, "Build doesn't have a product $productnr.") if !defined $product;
|
||||||
|
|
||||||
|
return error($c, "Product " . $product->path . " has disappeared.") unless -e $product->path;
|
||||||
|
|
||||||
|
# Security paranoia.
|
||||||
|
foreach my $elem (@path) {
|
||||||
|
return error($c, "Invalid filename $elem.") if $elem !~ /^$pathCompRE$/;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $path = $product->path;
|
||||||
|
$path .= "/" . join("/", @path) if scalar @path > 0;
|
||||||
|
|
||||||
|
# If this is a directory but no "/" is attached, then redirect.
|
||||||
|
if (-d $path && substr($c->request->uri, -1) ne "/") {
|
||||||
|
return $c->res->redirect($c->request->uri . "/");
|
||||||
|
}
|
||||||
|
|
||||||
|
$path = "$path/index.html" if -d $path && -e "$path/index.html";
|
||||||
|
|
||||||
|
if (!-e $path) {
|
||||||
|
return error($c, "File $path does not exist.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$c->serve_static_file($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
1;
|
|
@ -4,17 +4,13 @@ use strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
use parent 'Catalyst::Controller';
|
use parent 'Catalyst::Controller';
|
||||||
use Hydra::Helper::Nix;
|
use Hydra::Helper::Nix;
|
||||||
|
use Hydra::Helper::CatalystUtils;
|
||||||
|
|
||||||
|
|
||||||
# Put this controller at top-level.
|
# Put this controller at top-level.
|
||||||
__PACKAGE__->config->{namespace} = '';
|
__PACKAGE__->config->{namespace} = '';
|
||||||
|
|
||||||
|
|
||||||
# Security checking of filenames.
|
|
||||||
my $pathCompRE = "(?:[A-Za-z0-9-\+][A-Za-z0-9-\+\._]*)";
|
|
||||||
my $relPathRE = "(?:$pathCompRE(?:\/$pathCompRE)*)";
|
|
||||||
|
|
||||||
|
|
||||||
sub begin :Private {
|
sub begin :Private {
|
||||||
my ($self, $c) = @_;
|
my ($self, $c) = @_;
|
||||||
$c->stash->{projects} = [$c->model('DB::Projects')->search({}, {order_by => 'displayname'})];
|
$c->stash->{projects} = [$c->model('DB::Projects')->search({}, {order_by => 'displayname'})];
|
||||||
|
@ -22,13 +18,6 @@ sub begin :Private {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
sub error {
|
|
||||||
my ($c, $msg) = @_;
|
|
||||||
$c->error($msg);
|
|
||||||
$c->detach;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
sub trim {
|
sub trim {
|
||||||
my $s = shift;
|
my $s = shift;
|
||||||
$s =~ s/^\s+|\s+$//g;
|
$s =~ s/^\s+|\s+$//g;
|
||||||
|
@ -36,13 +25,6 @@ sub trim {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
sub getBuild {
|
|
||||||
my ($c, $id) = @_;
|
|
||||||
my $build = $c->model('DB::Builds')->find($id);
|
|
||||||
return $build;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
sub getBuildStats {
|
sub getBuildStats {
|
||||||
my ($c, $builds) = @_;
|
my ($c, $builds) = @_;
|
||||||
|
|
||||||
|
@ -368,7 +350,7 @@ sub updateProject {
|
||||||
|
|
||||||
# The Nix expression path must be relative and can't contain ".." elements.
|
# The Nix expression path must be relative and can't contain ".." elements.
|
||||||
my $nixExprPath = trim $c->request->params->{"jobset-$baseName-nixexprpath"};
|
my $nixExprPath = trim $c->request->params->{"jobset-$baseName-nixexprpath"};
|
||||||
die "Invalid Nix expression path: $nixExprPath" if $nixExprPath !~ /^$relPathRE$/;
|
die "Invalid Nix expression path: $nixExprPath" if $nixExprPath !~ /^$Build::relPathRE$/;
|
||||||
|
|
||||||
my $nixExprInput = trim $c->request->params->{"jobset-$baseName-nixexprinput"};
|
my $nixExprInput = trim $c->request->params->{"jobset-$baseName-nixexprinput"};
|
||||||
die "Invalid Nix expression input name: $nixExprInput" unless $nixExprInput =~ /^\w+$/;
|
die "Invalid Nix expression input name: $nixExprInput" unless $nixExprInput =~ /^\w+$/;
|
||||||
|
@ -573,110 +555,6 @@ sub default :Path {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
sub build : Chained('/') PathPart CaptureArgs(1) {
|
|
||||||
my ($self, $c, $id) = @_;
|
|
||||||
|
|
||||||
$c->stash->{id} = $id;
|
|
||||||
|
|
||||||
$c->stash->{build} = getBuild($c, $id);
|
|
||||||
|
|
||||||
if (!defined $c->stash->{build}) {
|
|
||||||
error($c, "Build with ID $id doesn't exist.");
|
|
||||||
$c->response->status(404);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$c->stash->{curProject} = $c->stash->{build}->project;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
sub view_build : Chained('build') PathPart('') Args(0) {
|
|
||||||
my ($self, $c) = @_;
|
|
||||||
|
|
||||||
my $build = $c->stash->{build};
|
|
||||||
|
|
||||||
$c->stash->{template} = 'build.tt';
|
|
||||||
$c->stash->{curTime} = time;
|
|
||||||
$c->stash->{available} = isValidPath $build->outpath;
|
|
||||||
|
|
||||||
if (!$build->finished && $build->schedulingInfo->busy) {
|
|
||||||
my $logfile = $build->schedulingInfo->logfile;
|
|
||||||
$c->stash->{logtext} = `cat $logfile`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
sub view_nixlog : Chained('build') PathPart('nixlog') Args(1) {
|
|
||||||
my ($self, $c, $stepnr) = @_;
|
|
||||||
|
|
||||||
my $step = $c->stash->{build}->buildsteps->find({stepnr => $stepnr});
|
|
||||||
return error($c, "Build doesn't have a build step $stepnr.") if !defined $step;
|
|
||||||
|
|
||||||
$c->stash->{template} = 'log.tt';
|
|
||||||
$c->stash->{step} = $step;
|
|
||||||
|
|
||||||
# !!! should be done in the view (as a TT plugin).
|
|
||||||
$c->stash->{logtext} = loadLog($c, $step->logfile);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
sub view_log : Chained('build') PathPart('log') Args(0) {
|
|
||||||
my ($self, $c) = @_;
|
|
||||||
|
|
||||||
return error($c, "Build didn't produce a log.") if !defined $c->stash->{build}->resultInfo->logfile;
|
|
||||||
|
|
||||||
$c->stash->{template} = 'log.tt';
|
|
||||||
|
|
||||||
# !!! should be done in the view (as a TT plugin).
|
|
||||||
$c->stash->{logtext} = loadLog($c, $c->stash->{build}->resultInfo->logfile);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
sub loadLog {
|
|
||||||
my ($c, $path) = @_;
|
|
||||||
|
|
||||||
die unless defined $path;
|
|
||||||
|
|
||||||
# !!! quick hack
|
|
||||||
my $pipeline = ($path =~ /.bz2$/ ? "cat $path | bzip2 -d" : "cat $path")
|
|
||||||
. " | nix-log2xml | xsltproc " . $c->path_to("xsl/mark-errors.xsl") . " -"
|
|
||||||
. " | xsltproc " . $c->path_to("xsl/log2html.xsl") . " - | tail -n +2";
|
|
||||||
|
|
||||||
return `$pipeline`;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
sub download : Chained('build') PathPart('download') {
|
|
||||||
my ($self, $c, $productnr, $filename, @path) = @_;
|
|
||||||
|
|
||||||
my $product = $c->stash->{build}->buildproducts->find({productnr => $productnr});
|
|
||||||
return error($c, "Build doesn't have a product $productnr.") if !defined $product;
|
|
||||||
|
|
||||||
return error($c, "Product " . $product->path . " has disappeared.") unless -e $product->path;
|
|
||||||
|
|
||||||
# Security paranoia.
|
|
||||||
foreach my $elem (@path) {
|
|
||||||
return error($c, "Invalid filename $elem.") if $elem !~ /^$pathCompRE$/;
|
|
||||||
}
|
|
||||||
|
|
||||||
my $path = $product->path;
|
|
||||||
$path .= "/" . join("/", @path) if scalar @path > 0;
|
|
||||||
|
|
||||||
# If this is a directory but no "/" is attached, then redirect.
|
|
||||||
if (-d $path && substr($c->request->uri, -1) ne "/") {
|
|
||||||
return $c->res->redirect($c->request->uri . "/");
|
|
||||||
}
|
|
||||||
|
|
||||||
$path = "$path/index.html" if -d $path && -e "$path/index.html";
|
|
||||||
|
|
||||||
if (!-e $path) {
|
|
||||||
return error($c, "File $path does not exist.");
|
|
||||||
}
|
|
||||||
|
|
||||||
$c->serve_static_file($path);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
sub closure :Local {
|
sub closure :Local {
|
||||||
my ($self, $c, $buildId) = @_;
|
my ($self, $c, $buildId) = @_;
|
||||||
|
|
||||||
|
|
24
src/Hydra/lib/Hydra/Helper/CatalystUtils.pm
Normal file
24
src/Hydra/lib/Hydra/Helper/CatalystUtils.pm
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
package Hydra::Helper::CatalystUtils;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use Exporter;
|
||||||
|
|
||||||
|
our @ISA = qw(Exporter);
|
||||||
|
our @EXPORT = qw(getBuild error);
|
||||||
|
|
||||||
|
|
||||||
|
sub getBuild {
|
||||||
|
my ($c, $id) = @_;
|
||||||
|
my $build = $c->model('DB::Builds')->find($id);
|
||||||
|
return $build;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sub error {
|
||||||
|
my ($c, $msg) = @_;
|
||||||
|
$c->error($msg);
|
||||||
|
$c->detach;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
1;
|
|
@ -121,7 +121,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<th>Logfile:</th>
|
<th>Logfile:</th>
|
||||||
<td>
|
<td>
|
||||||
<a href="[% c.uri_for('build' build.id 'log') %]"><strong>Available</strong></a>
|
<a href="[% c.uri_for('/build' build.id 'log') %]"><strong>Available</strong></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
[% END %]
|
[% END %]
|
||||||
|
@ -178,7 +178,7 @@
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
[% FOREACH step IN build.buildsteps -%]
|
[% FOREACH step IN build.buildsteps -%]
|
||||||
[% log = c.uri_for('build' build.id 'nixlog' step.stepnr) %]
|
[% log = c.uri_for('/build' build.id 'nixlog' step.stepnr) %]
|
||||||
<tr class="[% IF step.logfile %]clickable[% END %]"
|
<tr class="[% IF step.logfile %]clickable[% END %]"
|
||||||
[% IF step.logfile %] onclick="window.location = '[% log %]'" [% END %]>
|
[% IF step.logfile %] onclick="window.location = '[% log %]'" [% END %]>
|
||||||
<td>[% step.stepnr %]</td>
|
<td>[% step.stepnr %]</td>
|
||||||
|
|
|
@ -6,14 +6,14 @@
|
||||||
|
|
||||||
[% FOREACH product IN build.buildproducts -%]
|
[% FOREACH product IN build.buildproducts -%]
|
||||||
|
|
||||||
[% uri = c.uri_for('build' build.id 'download' product.productnr product.name) %]
|
[% uri = c.uri_for('/build' build.id 'download' product.productnr product.name) %]
|
||||||
|
|
||||||
[% SWITCH product.type %]
|
[% SWITCH product.type %]
|
||||||
|
|
||||||
[% CASE "nix-build" %]
|
[% CASE "nix-build" %]
|
||||||
|
|
||||||
<li class="product">
|
<li class="product">
|
||||||
[% uri = c.uri_for('nixpkg' build.id) %]
|
[% uri = c.uri_for('/nixpkg' build.id) %]
|
||||||
<a href="[% uri %]">
|
<a href="[% uri %]">
|
||||||
<img src="/static/images/nix-build.png" alt="Source" />
|
<img src="/static/images/nix-build.png" alt="Source" />
|
||||||
One-click install of Nix package <tt>[% build.nixname %]</tt>
|
One-click install of Nix package <tt>[% build.nixname %]</tt>
|
||||||
|
|
Loading…
Reference in a new issue