2009-02-25 12:03:13 +00:00
|
|
|
|
package Hydra::Controller::Build;
|
|
|
|
|
|
2014-10-01 17:05:39 +02:00
|
|
|
|
use utf8;
|
2009-02-25 12:03:13 +00:00
|
|
|
|
use strict;
|
|
|
|
|
use warnings;
|
2009-03-04 12:23:54 +00:00
|
|
|
|
use base 'Hydra::Base::Controller::NixChannel';
|
2009-02-25 12:03:13 +00:00
|
|
|
|
use Hydra::Helper::Nix;
|
|
|
|
|
use Hydra::Helper::CatalystUtils;
|
2017-04-05 17:55:56 +02:00
|
|
|
|
use File::Basename;
|
2009-04-16 15:22:14 +00:00
|
|
|
|
use File::stat;
|
2010-01-22 13:31:59 +00:00
|
|
|
|
use Data::Dump qw(dump);
|
2011-11-30 15:25:28 +01:00
|
|
|
|
use Nix::Store;
|
2013-02-13 18:34:33 +01:00
|
|
|
|
use Nix::Config;
|
2021-12-14 10:08:30 -05:00
|
|
|
|
use List::SomeUtils qw(all);
|
2014-08-13 18:53:29 +02:00
|
|
|
|
use Encode;
|
2017-10-18 13:38:34 +02:00
|
|
|
|
use MIME::Types;
|
2017-11-14 17:15:05 +01:00
|
|
|
|
use JSON::PP;
|
2009-02-25 12:03:13 +00:00
|
|
|
|
|
2011-09-15 08:27:17 +00:00
|
|
|
|
|
2013-06-17 12:34:21 -04:00
|
|
|
|
sub buildChain :Chained('/') :PathPart('build') :CaptureArgs(1) {
|
2009-02-25 12:03:13 +00:00
|
|
|
|
my ($self, $c, $id) = @_;
|
2012-06-25 15:05:16 +02:00
|
|
|
|
|
2014-10-01 17:05:39 +02:00
|
|
|
|
$id =~ /^[0-9]+$/ or error($c, "Invalid build ID ‘$id’.");
|
|
|
|
|
|
2009-02-25 12:03:13 +00:00
|
|
|
|
$c->stash->{id} = $id;
|
2012-06-25 15:05:16 +02:00
|
|
|
|
|
2009-02-25 12:03:13 +00:00
|
|
|
|
$c->stash->{build} = getBuild($c, $id);
|
2010-03-05 13:03:41 +00:00
|
|
|
|
|
|
|
|
|
notFound($c, "Build with ID $id doesn't exist.")
|
|
|
|
|
if !defined $c->stash->{build};
|
|
|
|
|
|
2010-02-22 13:21:34 +00:00
|
|
|
|
$c->stash->{prevSuccessfulBuild} = getPreviousSuccessfulBuild($c, $c->stash->{build});
|
2010-07-14 07:31:14 +00:00
|
|
|
|
$c->stash->{firstBrokenBuild} = getNextBuild($c, $c->stash->{prevSuccessfulBuild});
|
2009-02-25 14:34:29 +00:00
|
|
|
|
|
2010-02-25 15:32:56 +00:00
|
|
|
|
$c->stash->{mappers} = [$c->model('DB::UriRevMapper')->all];
|
|
|
|
|
|
2009-03-13 15:41:19 +00:00
|
|
|
|
$c->stash->{project} = $c->stash->{build}->project;
|
2014-02-26 11:49:28 +01:00
|
|
|
|
$c->stash->{jobset} = $c->stash->{build}->jobset;
|
|
|
|
|
$c->stash->{job} = $c->stash->{build}->job;
|
2021-11-19 15:21:45 -05:00
|
|
|
|
$c->stash->{runcommandlogs} = [$c->stash->{build}->runcommandlogs->search({}, {order_by => ["id DESC"]})];
|
2009-02-25 12:03:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
2011-09-15 08:27:17 +00:00
|
|
|
|
|
2013-02-13 16:49:28 +00:00
|
|
|
|
sub findBuildStepByOutPath {
|
2013-08-30 13:53:25 +00:00
|
|
|
|
my ($self, $c, $path) = @_;
|
2013-02-13 16:49:28 +00:00
|
|
|
|
return $c->model('DB::BuildSteps')->search(
|
2013-08-30 13:53:25 +00:00
|
|
|
|
{ path => $path, busy => 0 },
|
|
|
|
|
{ join => ["buildstepoutputs"], order_by => ["status", "stopTime"], rows => 1 })->single;
|
2013-02-13 16:49:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sub findBuildStepByDrvPath {
|
2013-08-30 13:53:25 +00:00
|
|
|
|
my ($self, $c, $drvPath) = @_;
|
2013-02-13 16:49:28 +00:00
|
|
|
|
return $c->model('DB::BuildSteps')->search(
|
2013-08-30 13:53:25 +00:00
|
|
|
|
{ drvpath => $drvPath, busy => 0 },
|
|
|
|
|
{ order_by => ["status", "stopTime"], rows => 1 })->single;
|
2013-02-13 16:49:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2013-06-17 12:34:21 -04:00
|
|
|
|
sub build :Chained('buildChain') :PathPart('') :Args(0) :ActionClass('REST') { }
|
|
|
|
|
|
|
|
|
|
sub build_GET {
|
2009-02-25 12:03:13 +00:00
|
|
|
|
my ($self, $c) = @_;
|
|
|
|
|
|
|
|
|
|
my $build = $c->stash->{build};
|
2012-06-25 15:05:16 +02:00
|
|
|
|
|
2009-02-25 12:03:13 +00:00
|
|
|
|
$c->stash->{template} = 'build.tt';
|
2017-10-18 13:44:41 +02:00
|
|
|
|
$c->stash->{isLocalStore} = isLocalStore();
|
2016-02-26 17:28:26 +01:00
|
|
|
|
$c->stash->{available} =
|
2017-10-18 13:44:41 +02:00
|
|
|
|
$c->stash->{isLocalStore}
|
2016-02-26 17:28:26 +01:00
|
|
|
|
? all { isValidPath($_->path) } $build->buildoutputs->all
|
2017-10-18 12:23:07 +02:00
|
|
|
|
: 1;
|
2009-02-27 15:31:49 +00:00
|
|
|
|
$c->stash->{drvAvailable} = isValidPath $build->drvpath;
|
2009-02-25 12:03:13 +00:00
|
|
|
|
|
2012-03-05 21:52:47 +01:00
|
|
|
|
if ($build->finished && $build->iscachedbuild) {
|
2013-02-13 16:49:28 +00:00
|
|
|
|
my $path = ($build->buildoutputs)[0]->path or die;
|
2013-08-30 13:53:25 +00:00
|
|
|
|
my $cachedBuildStep = findBuildStepByOutPath($self, $c, $path);
|
2017-04-11 14:25:48 +02:00
|
|
|
|
if (defined $cachedBuildStep) {
|
|
|
|
|
$c->stash->{cachedBuild} = $cachedBuildStep->build;
|
|
|
|
|
$c->stash->{cachedBuildStep} = $cachedBuildStep;
|
|
|
|
|
}
|
2010-01-15 14:18:12 +00:00
|
|
|
|
}
|
2012-06-25 15:05:16 +02:00
|
|
|
|
|
2013-02-21 18:34:34 +01:00
|
|
|
|
# Get the first eval of which this build was a part.
|
2015-12-15 11:55:57 +01:00
|
|
|
|
($c->stash->{nrEvals}) = $build->jobsetevals->search({ hasnewbuilds => 1 })->count;
|
2015-05-26 15:54:38 +02:00
|
|
|
|
$c->stash->{eval} = getFirstEval($build);
|
2013-06-17 12:34:21 -04:00
|
|
|
|
$self->status_ok(
|
|
|
|
|
$c,
|
2013-10-24 15:47:36 -04:00
|
|
|
|
entity => $build
|
2013-06-17 12:34:21 -04:00
|
|
|
|
);
|
2013-08-15 03:28:21 +02:00
|
|
|
|
|
2016-02-12 16:27:25 +01:00
|
|
|
|
if (defined $c->stash->{eval}) {
|
|
|
|
|
my ($eval2) = $c->stash->{eval}->jobset->jobsetevals->search(
|
|
|
|
|
{ hasnewbuilds => 1, id => { '<', $c->stash->{eval}->id } },
|
|
|
|
|
{ order_by => "id DESC", rows => 1 });
|
|
|
|
|
$c->stash->{otherEval} = $eval2 if defined $eval2;
|
|
|
|
|
}
|
2014-11-18 11:00:15 +01:00
|
|
|
|
|
2013-08-15 03:28:21 +02:00
|
|
|
|
# If this is an aggregate build, get its constituents.
|
2015-12-15 11:55:57 +01:00
|
|
|
|
$c->stash->{constituents} = [$build->constituents_->search({}, {order_by => ["job"]})];
|
|
|
|
|
|
|
|
|
|
$c->stash->{steps} = [$build->buildsteps->search({}, {order_by => "stepnr desc"})];
|
2018-01-15 14:27:58 +01:00
|
|
|
|
|
|
|
|
|
$c->stash->{binaryCachePublicUri} = $c->config->{binary_cache_public_uri};
|
2009-02-25 12:03:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-09-24 15:52:46 -04:00
|
|
|
|
sub constituents :Chained('buildChain') :PathPart('constituents') :Args(0) :ActionClass('REST') { }
|
|
|
|
|
|
|
|
|
|
sub constituents_GET {
|
|
|
|
|
my ($self, $c) = @_;
|
|
|
|
|
|
|
|
|
|
my $build = $c->stash->{build};
|
|
|
|
|
|
|
|
|
|
$self->status_ok(
|
|
|
|
|
$c,
|
|
|
|
|
entity => [$build->constituents_->search({}, {order_by => ["job"]})]
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2009-02-25 12:03:13 +00:00
|
|
|
|
|
2013-06-17 12:34:21 -04:00
|
|
|
|
sub view_nixlog : Chained('buildChain') PathPart('nixlog') {
|
2009-03-14 23:27:08 +00:00
|
|
|
|
my ($self, $c, $stepnr, $mode) = @_;
|
2009-02-25 12:03:13 +00:00
|
|
|
|
|
|
|
|
|
my $step = $c->stash->{build}->buildsteps->find({stepnr => $stepnr});
|
2009-02-25 14:34:29 +00:00
|
|
|
|
notFound($c, "Build doesn't have a build step $stepnr.") if !defined $step;
|
2009-02-25 12:03:13 +00:00
|
|
|
|
|
|
|
|
|
$c->stash->{step} = $step;
|
|
|
|
|
|
2022-01-26 12:36:25 -08:00
|
|
|
|
my $drvPath = $step->drvpath;
|
|
|
|
|
my $log_uri = $c->uri_for($c->controller('Root')->action_for("log"), [basename($drvPath)]);
|
|
|
|
|
showLog($c, $mode, $log_uri);
|
2009-02-25 12:03:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2013-06-17 12:34:21 -04:00
|
|
|
|
sub view_log : Chained('buildChain') PathPart('log') {
|
2009-03-14 23:27:08 +00:00
|
|
|
|
my ($self, $c, $mode) = @_;
|
2022-01-26 12:36:25 -08:00
|
|
|
|
|
|
|
|
|
my $drvPath = $c->stash->{build}->drvpath;
|
|
|
|
|
my $log_uri = $c->uri_for($c->controller('Root')->action_for("log"), [basename($drvPath)]);
|
|
|
|
|
showLog($c, $mode, $log_uri);
|
2009-02-25 12:03:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2022-01-26 10:26:28 -08:00
|
|
|
|
sub view_runcommandlog : Chained('buildChain') PathPart('runcommandlog') {
|
2022-01-26 12:38:06 -08:00
|
|
|
|
my ($self, $c, $uuid, $mode) = @_;
|
2021-12-26 16:14:28 +01:00
|
|
|
|
|
2022-01-26 12:38:06 -08:00
|
|
|
|
my $log_uri = $c->uri_for($c->controller('Root')->action_for("runcommandlog"), $uuid);
|
|
|
|
|
showLog($c, $mode, $log_uri);
|
2022-01-24 11:05:49 -08:00
|
|
|
|
$c->stash->{template} = 'runcommand-log.tt';
|
2022-01-26 12:38:06 -08:00
|
|
|
|
$c->stash->{runcommandlog} = $c->stash->{build}->runcommandlogs->find({ uuid => $uuid });
|
2021-12-26 16:14:28 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2009-03-14 23:27:08 +00:00
|
|
|
|
sub showLog {
|
2022-01-26 12:36:25 -08:00
|
|
|
|
my ($c, $mode, $log_uri) = @_;
|
2015-10-09 15:06:57 +02:00
|
|
|
|
$mode //= "pretty";
|
2009-02-25 12:03:13 +00:00
|
|
|
|
|
2015-10-09 15:06:57 +02:00
|
|
|
|
if ($mode eq "pretty") {
|
2017-04-05 17:55:56 +02:00
|
|
|
|
$c->stash->{log_uri} = $log_uri;
|
2009-03-14 23:27:08 +00:00
|
|
|
|
$c->stash->{template} = 'log.tt';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
elsif ($mode eq "raw") {
|
2017-04-05 17:55:56 +02:00
|
|
|
|
$c->res->redirect($log_uri);
|
2011-02-02 09:00:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
2009-03-16 12:16:33 +00:00
|
|
|
|
elsif ($mode eq "tail") {
|
2017-04-05 17:55:56 +02:00
|
|
|
|
my $lines = 50;
|
|
|
|
|
$c->stash->{log_uri} = $log_uri . "?tail=$lines";
|
|
|
|
|
$c->stash->{tail} = $lines;
|
|
|
|
|
$c->stash->{template} = 'log.tt';
|
2009-03-16 12:16:33 +00:00
|
|
|
|
}
|
|
|
|
|
|
2009-03-14 23:27:08 +00:00
|
|
|
|
else {
|
2017-04-05 17:55:56 +02:00
|
|
|
|
error($c, "Unknown log display mode '$mode'.");
|
2009-03-14 23:27:08 +00:00
|
|
|
|
}
|
2009-02-25 12:03:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2009-04-08 08:09:39 +00:00
|
|
|
|
sub defaultUriForProduct {
|
|
|
|
|
my ($self, $c, $product, @path) = @_;
|
|
|
|
|
my $x = $product->productnr
|
|
|
|
|
. ($product->name ? "/" . $product->name : "")
|
2012-06-25 15:05:16 +02:00
|
|
|
|
. ($product->defaultpath ? "/" . $product->defaultpath : "");
|
2011-08-19 16:23:01 +00:00
|
|
|
|
return $c->uri_for($self->action_for("download"), $c->req->captures, (split /\//, $x), @path);
|
2009-04-08 08:09:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2013-02-13 18:34:33 +01:00
|
|
|
|
sub checkPath {
|
|
|
|
|
my ($self, $c, $path) = @_;
|
2013-06-02 23:32:09 +02:00
|
|
|
|
my $p = pathIsInsidePrefix($path, $Nix::Config::storeDir);
|
|
|
|
|
error($c, "Build product refers outside of the Nix store.") unless defined $p;
|
|
|
|
|
return $p;
|
2013-02-13 18:34:33 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2017-11-14 17:15:05 +01:00
|
|
|
|
sub serveFile {
|
|
|
|
|
my ($c, $path) = @_;
|
|
|
|
|
|
2020-03-03 22:46:32 -05:00
|
|
|
|
my $res = run(cmd => ["nix", "--experimental-features", "nix-command",
|
|
|
|
|
"ls-store", "--store", getStoreUri(), "--json", "$path"]);
|
2017-11-14 17:15:05 +01:00
|
|
|
|
|
|
|
|
|
if ($res->{status}) {
|
|
|
|
|
notFound($c, "File '$path' does not exist.") if $res->{stderr} =~ /does not exist/;
|
|
|
|
|
die "$res->{stderr}\n";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
my $ls = decode_json($res->{stdout});
|
|
|
|
|
|
|
|
|
|
if ($ls->{type} eq "directory" && substr($c->request->uri, -1) ne "/") {
|
|
|
|
|
return $c->res->redirect($c->request->uri . "/");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
elsif ($ls->{type} eq "directory" && defined $ls->{entries}->{"index.html"}) {
|
|
|
|
|
return serveFile($c, "$path/index.html");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
elsif ($ls->{type} eq "symlink") {
|
|
|
|
|
my $target = $ls->{target};
|
|
|
|
|
return serveFile($c, substr($target, 0, 1) eq "/" ? $target : dirname($path) . "/" . $target);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
elsif ($ls->{type} eq "regular") {
|
|
|
|
|
|
2020-03-03 22:46:32 -05:00
|
|
|
|
$c->stash->{'plain'} = { data => grab(cmd => ["nix", "--experimental-features", "nix-command",
|
|
|
|
|
"cat-store", "--store", getStoreUri(), "$path"]) };
|
2017-11-14 17:15:05 +01:00
|
|
|
|
|
|
|
|
|
# Detect MIME type. Borrowed from Catalyst::Plugin::Static::Simple.
|
|
|
|
|
my $type = "text/plain";
|
|
|
|
|
if ($path =~ /.*\.(\S{1,})$/xms) {
|
|
|
|
|
my $ext = $1;
|
|
|
|
|
my $mimeTypes = MIME::Types->new(only_complete => 1);
|
|
|
|
|
my $t = $mimeTypes->mimeTypeOf($ext);
|
|
|
|
|
$type = ref $t ? $t->type : $t if $t;
|
|
|
|
|
}
|
|
|
|
|
$c->response->content_type($type);
|
|
|
|
|
$c->forward('Hydra::View::Plain');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else {
|
|
|
|
|
error($c, "Do not know how to serve path '$path'.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2013-06-17 12:34:21 -04:00
|
|
|
|
sub download : Chained('buildChain') PathPart {
|
2015-07-31 17:50:22 +02:00
|
|
|
|
my ($self, $c, $productRef, @path) = @_;
|
2009-02-25 12:03:13 +00:00
|
|
|
|
|
2015-07-31 17:50:22 +02:00
|
|
|
|
$productRef = 1 if !defined $productRef;
|
2009-04-08 09:37:05 +00:00
|
|
|
|
|
2015-07-31 17:50:22 +02:00
|
|
|
|
my $product;
|
|
|
|
|
if ($productRef =~ /^[0-9]+$/) {
|
|
|
|
|
$product = $c->stash->{build}->buildproducts->find({productnr => $productRef});
|
|
|
|
|
} else {
|
|
|
|
|
$product = $c->stash->{build}->buildproducts->find({name => $productRef});
|
|
|
|
|
@path = ($productRef, @path);
|
|
|
|
|
}
|
|
|
|
|
notFound($c, "Build doesn't have a product $productRef.") if !defined $product;
|
2009-02-25 12:03:13 +00:00
|
|
|
|
|
2016-02-26 17:28:26 +01:00
|
|
|
|
if ($product->path !~ /^($Nix::Config::storeDir\/[^\/]+)/) {
|
2017-11-14 17:15:05 +01:00
|
|
|
|
die "Invalid store path '" . $product->path . "'.\n";
|
2016-02-26 17:28:26 +01:00
|
|
|
|
}
|
|
|
|
|
my $storePath = $1;
|
|
|
|
|
|
2009-04-08 08:09:39 +00:00
|
|
|
|
return $c->res->redirect(defaultUriForProduct($self, $c, $product, @path))
|
|
|
|
|
if scalar @path == 0 && ($product->name || $product->defaultpath);
|
2012-06-25 15:05:16 +02:00
|
|
|
|
|
2009-03-06 13:34:53 +00:00
|
|
|
|
# If the product has a name, then the first path element can be
|
|
|
|
|
# ignored (it's the name included in the URL for informational purposes).
|
2012-06-25 15:05:16 +02:00
|
|
|
|
shift @path if $product->name;
|
|
|
|
|
|
2009-02-25 12:03:13 +00:00
|
|
|
|
# Security paranoia.
|
|
|
|
|
foreach my $elem (@path) {
|
2017-11-14 17:15:05 +01:00
|
|
|
|
error($c, "Invalid filename '$elem'.") if $elem !~ /^$pathCompRE$/;
|
2009-02-25 12:03:13 +00:00
|
|
|
|
}
|
2012-06-25 15:05:16 +02:00
|
|
|
|
|
2009-02-25 12:03:13 +00:00
|
|
|
|
my $path = $product->path;
|
|
|
|
|
$path .= "/" . join("/", @path) if scalar @path > 0;
|
|
|
|
|
|
2017-10-18 12:48:31 +02:00
|
|
|
|
if (isLocalStore) {
|
2013-02-13 18:34:33 +01:00
|
|
|
|
|
2017-11-14 17:15:05 +01:00
|
|
|
|
notFound($c, "File '" . $product->path . "' does not exist.") unless -e $product->path;
|
2017-10-18 12:48:31 +02:00
|
|
|
|
|
|
|
|
|
# Make sure the file is in the Nix store.
|
|
|
|
|
$path = checkPath($self, $c, $path);
|
|
|
|
|
|
|
|
|
|
# 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";
|
2012-06-25 15:05:16 +02:00
|
|
|
|
|
2017-11-14 17:15:05 +01:00
|
|
|
|
notFound($c, "File '$path' does not exist.") if !-e $path;
|
2009-02-25 12:03:13 +00:00
|
|
|
|
|
2017-11-14 17:15:05 +01:00
|
|
|
|
notFound($c, "Path '$path' is a directory.") if -d $path;
|
2009-02-25 12:03:13 +00:00
|
|
|
|
|
2017-10-18 12:48:31 +02:00
|
|
|
|
$c->serve_static_file($path);
|
2009-03-06 13:34:53 +00:00
|
|
|
|
|
2017-10-18 12:48:31 +02:00
|
|
|
|
} else {
|
2017-11-14 17:15:05 +01:00
|
|
|
|
serveFile($c, $path);
|
2017-10-18 12:48:31 +02:00
|
|
|
|
}
|
2017-11-14 17:15:05 +01:00
|
|
|
|
|
|
|
|
|
$c->response->headers->last_modified($c->stash->{build}->stoptime);
|
2009-02-25 12:03:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2013-08-21 14:30:38 +02:00
|
|
|
|
sub output : Chained('buildChain') PathPart Args(1) {
|
|
|
|
|
my ($self, $c, $outputName) = @_;
|
|
|
|
|
my $build = $c->stash->{build};
|
|
|
|
|
|
|
|
|
|
error($c, "This build is not finished yet.") unless $build->finished;
|
|
|
|
|
my $output = $build->buildoutputs->find({name => $outputName});
|
|
|
|
|
notFound($c, "This build has no output named ‘$outputName’") unless defined $output;
|
2014-02-26 11:38:02 +01:00
|
|
|
|
gone($c, "Output is no longer available.") unless isValidPath $output->path;
|
2013-08-21 14:30:38 +02:00
|
|
|
|
|
|
|
|
|
$c->response->header('Content-Disposition', "attachment; filename=\"build-${\$build->id}-${\$outputName}.nar.bz2\"");
|
|
|
|
|
$c->stash->{current_view} = 'NixNAR';
|
|
|
|
|
$c->stash->{storePath} = $output->path;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2009-04-08 08:09:39 +00:00
|
|
|
|
# Redirect to a download with the given type. Useful when you want to
|
|
|
|
|
# link to some build product of the latest build (i.e. in conjunction
|
|
|
|
|
# with the .../latest redirect).
|
2013-06-17 12:34:21 -04:00
|
|
|
|
sub download_by_type : Chained('buildChain') PathPart('download-by-type') {
|
2009-04-08 08:09:39 +00:00
|
|
|
|
my ($self, $c, $type, $subtype, @path) = @_;
|
|
|
|
|
|
|
|
|
|
notFound($c, "You need to specify a type and a subtype in the URI.")
|
|
|
|
|
unless defined $type && defined $subtype;
|
|
|
|
|
|
|
|
|
|
(my $product) = $c->stash->{build}->buildproducts->search(
|
2009-10-12 17:07:36 +00:00
|
|
|
|
{type => $type, subtype => $subtype}, {order_by => "productnr"});
|
2009-04-08 08:09:39 +00:00
|
|
|
|
notFound($c, "Build doesn't have a build product with type $type/$subtype.")
|
|
|
|
|
if !defined $product;
|
|
|
|
|
|
|
|
|
|
$c->res->redirect(defaultUriForProduct($self, $c, $product, @path));
|
|
|
|
|
}
|
|
|
|
|
|
2011-09-15 08:27:17 +00:00
|
|
|
|
|
2013-06-17 12:34:21 -04:00
|
|
|
|
sub contents : Chained('buildChain') PathPart Args(1) {
|
2009-03-18 18:50:42 +00:00
|
|
|
|
my ($self, $c, $productnr) = @_;
|
2009-03-18 17:40:12 +00:00
|
|
|
|
|
|
|
|
|
my $product = $c->stash->{build}->buildproducts->find({productnr => $productnr});
|
|
|
|
|
notFound($c, "Build doesn't have a product $productnr.") if !defined $product;
|
|
|
|
|
|
|
|
|
|
my $path = $product->path;
|
2012-06-25 15:05:16 +02:00
|
|
|
|
|
2013-04-02 23:32:04 +02:00
|
|
|
|
$path = checkPath($self, $c, $path);
|
2013-02-13 18:34:33 +01:00
|
|
|
|
|
2009-03-18 17:40:12 +00:00
|
|
|
|
notFound($c, "Product $path has disappeared.") unless -e $path;
|
|
|
|
|
|
2013-04-03 00:11:37 +02:00
|
|
|
|
# Sanitize $path to prevent shell injection attacks.
|
2013-05-27 12:42:52 +02:00
|
|
|
|
$path =~ /^\/[\/[A-Za-z0-9_\-\.=+:]+$/ or die "Filename contains illegal characters.\n";
|
2013-04-03 00:11:37 +02:00
|
|
|
|
|
|
|
|
|
# FIXME: don't use shell invocations below.
|
|
|
|
|
|
2017-11-14 17:15:05 +01:00
|
|
|
|
# FIXME: use nix cat-store
|
|
|
|
|
|
2009-03-18 17:40:12 +00:00
|
|
|
|
my $res;
|
|
|
|
|
|
2010-02-05 20:07:49 +00:00
|
|
|
|
if ($product->type eq "nix-build" && -d $path) {
|
2017-11-14 17:15:05 +01:00
|
|
|
|
# FIXME: use nix ls-store -R --json
|
2013-04-03 00:11:37 +02:00
|
|
|
|
$res = `cd '$path' && find . -print0 | xargs -0 ls -ld --`;
|
2009-03-18 17:40:12 +00:00
|
|
|
|
error($c, "`ls -lR' error: $?") if $? != 0;
|
2012-06-25 15:05:16 +02:00
|
|
|
|
|
2014-08-17 23:43:22 +02:00
|
|
|
|
#my $baseuri = $c->uri_for('/build', $c->stash->{build}->id, 'download', $product->productnr);
|
|
|
|
|
#$baseuri .= "/".$product->name if $product->name;
|
|
|
|
|
#$res =~ s/(\.\/)($relPathRE)/<a href="$baseuri\/$2">$1$2<\/a>/g;
|
2009-03-18 17:40:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
elsif ($path =~ /\.rpm$/) {
|
2013-04-03 00:11:37 +02:00
|
|
|
|
$res = `rpm --query --info --package '$path'`;
|
2009-03-18 17:40:12 +00:00
|
|
|
|
error($c, "RPM error: $?") if $? != 0;
|
|
|
|
|
$res .= "===\n";
|
2013-04-03 00:11:37 +02:00
|
|
|
|
$res .= `rpm --query --list --verbose --package '$path'`;
|
2009-03-18 17:40:12 +00:00
|
|
|
|
error($c, "RPM error: $?") if $? != 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
elsif ($path =~ /\.deb$/) {
|
2013-04-03 00:11:37 +02:00
|
|
|
|
$res = `dpkg-deb --info '$path'`;
|
2009-03-18 17:40:12 +00:00
|
|
|
|
error($c, "`dpkg-deb' error: $?") if $? != 0;
|
|
|
|
|
$res .= "===\n";
|
2013-04-03 00:11:37 +02:00
|
|
|
|
$res .= `dpkg-deb --contents '$path'`;
|
2009-03-18 17:40:12 +00:00
|
|
|
|
error($c, "`dpkg-deb' error: $?") if $? != 0;
|
|
|
|
|
}
|
|
|
|
|
|
2011-11-16 10:32:32 -05:00
|
|
|
|
elsif ($path =~ /\.(tar(\.gz|\.bz2|\.xz|\.lzma)?|tgz)$/ ) {
|
2013-04-03 00:11:37 +02:00
|
|
|
|
$res = `tar tvfa '$path'`;
|
2009-03-18 17:40:12 +00:00
|
|
|
|
error($c, "`tar' error: $?") if $? != 0;
|
|
|
|
|
}
|
|
|
|
|
|
2009-06-18 13:23:04 +00:00
|
|
|
|
elsif ($path =~ /\.(zip|jar)$/ ) {
|
2013-04-03 00:11:37 +02:00
|
|
|
|
$res = `unzip -v '$path'`;
|
2009-03-18 17:40:12 +00:00
|
|
|
|
error($c, "`unzip' error: $?") if $? != 0;
|
2009-07-07 11:37:47 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
elsif ($path =~ /\.iso$/ ) {
|
2013-04-03 00:11:37 +02:00
|
|
|
|
$res = `isoinfo -d -i '$path' && isoinfo -l -R -i '$path'`;
|
2009-07-07 11:37:47 +00:00
|
|
|
|
error($c, "`isoinfo' error: $?") if $? != 0;
|
2009-03-18 17:40:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
else {
|
|
|
|
|
error($c, "Unsupported file type.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
die unless $res;
|
2012-06-25 15:05:16 +02:00
|
|
|
|
|
2011-01-04 12:50:59 +00:00
|
|
|
|
$c->stash->{title} = "Contents of ".$product->path;
|
2014-08-17 23:43:22 +02:00
|
|
|
|
$c->stash->{contents} = decode("utf-8", $res);
|
2011-01-04 12:50:59 +00:00
|
|
|
|
$c->stash->{template} = 'plain.tt';
|
2009-03-18 17:40:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2013-02-14 15:53:34 +01:00
|
|
|
|
sub getDependencyGraph {
|
|
|
|
|
my ($self, $c, $runtime, $done, $path) = @_;
|
|
|
|
|
my $node = $$done{$path};
|
|
|
|
|
|
|
|
|
|
if (!defined $node) {
|
|
|
|
|
$path =~ /\/[a-z0-9]+-(.*)$/;
|
|
|
|
|
my $name = $1 // $path;
|
|
|
|
|
$name =~ s/\.drv$//;
|
|
|
|
|
$node =
|
|
|
|
|
{ path => $path
|
|
|
|
|
, name => $name
|
|
|
|
|
, buildStep => $runtime
|
2013-08-30 13:53:25 +00:00
|
|
|
|
? findBuildStepByOutPath($self, $c, $path)
|
|
|
|
|
: findBuildStepByDrvPath($self, $c, $path)
|
2013-02-14 15:53:34 +01:00
|
|
|
|
};
|
|
|
|
|
$$done{$path} = $node;
|
|
|
|
|
my @refs;
|
|
|
|
|
foreach my $ref (queryReferences($path)) {
|
|
|
|
|
next if $ref eq $path;
|
|
|
|
|
next unless $runtime || $ref =~ /\.drv$/;
|
|
|
|
|
getDependencyGraph($self, $c, $runtime, $done, $ref);
|
|
|
|
|
push @refs, $ref;
|
|
|
|
|
}
|
|
|
|
|
# Show in reverse topological order to flatten the graph.
|
|
|
|
|
# Should probably do a proper BFS.
|
|
|
|
|
my @sorted = reverse topoSortPaths(@refs);
|
|
|
|
|
$node->{refs} = [map { $$done{$_} } @sorted];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $node;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2013-06-17 12:34:21 -04:00
|
|
|
|
sub build_deps : Chained('buildChain') PathPart('build-deps') {
|
2010-01-22 13:31:59 +00:00
|
|
|
|
my ($self, $c) = @_;
|
|
|
|
|
my $build = $c->stash->{build};
|
2013-02-13 16:49:28 +00:00
|
|
|
|
my $drvPath = $build->drvpath;
|
2010-01-22 13:31:59 +00:00
|
|
|
|
|
2013-02-20 17:58:27 +01:00
|
|
|
|
error($c, "Derivation no longer available.") unless isValidPath $drvPath;
|
2012-06-25 15:05:16 +02:00
|
|
|
|
|
2013-02-20 17:58:27 +01:00
|
|
|
|
$c->stash->{buildTimeGraph} = getDependencyGraph($self, $c, 0, {}, $drvPath);
|
2010-01-22 13:31:59 +00:00
|
|
|
|
|
2013-02-20 17:58:27 +01:00
|
|
|
|
$c->stash->{template} = 'build-deps.tt';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2013-06-17 12:34:21 -04:00
|
|
|
|
sub runtime_deps : Chained('buildChain') PathPart('runtime-deps') {
|
2013-02-20 17:58:27 +01:00
|
|
|
|
my ($self, $c) = @_;
|
|
|
|
|
my $build = $c->stash->{build};
|
|
|
|
|
my @outPaths = map { $_->path } $build->buildoutputs->all;
|
|
|
|
|
|
2016-03-02 15:08:53 +01:00
|
|
|
|
requireLocalStore($c);
|
|
|
|
|
|
2013-02-20 17:58:27 +01:00
|
|
|
|
error($c, "Build outputs no longer available.") unless all { isValidPath($_) } @outPaths;
|
|
|
|
|
|
|
|
|
|
my $done = {};
|
|
|
|
|
$c->stash->{runtimeGraph} = [ map { getDependencyGraph($self, $c, 1, $done, $_) } @outPaths ];
|
2010-01-22 13:31:59 +00:00
|
|
|
|
|
2013-02-20 17:58:27 +01:00
|
|
|
|
$c->stash->{template} = 'runtime-deps.tt';
|
2010-01-22 13:31:59 +00:00
|
|
|
|
}
|
|
|
|
|
|
2009-02-27 15:31:49 +00:00
|
|
|
|
|
2013-06-17 12:34:21 -04:00
|
|
|
|
sub nix : Chained('buildChain') PathPart('nix') CaptureArgs(0) {
|
2009-02-25 14:34:29 +00:00
|
|
|
|
my ($self, $c) = @_;
|
|
|
|
|
|
|
|
|
|
my $build = $c->stash->{build};
|
|
|
|
|
|
2009-02-27 14:57:06 +00:00
|
|
|
|
notFound($c, "Build cannot be downloaded as a closure or Nix package.")
|
2013-02-13 16:49:28 +00:00
|
|
|
|
if $build->buildproducts->search({type => "nix-build"})->count == 0;
|
2009-02-25 14:34:29 +00:00
|
|
|
|
|
2017-10-18 14:09:28 +02:00
|
|
|
|
if (isLocalStore) {
|
|
|
|
|
foreach my $out ($build->buildoutputs) {
|
|
|
|
|
notFound($c, "Path " . $out->path . " is no longer available.")
|
|
|
|
|
unless isValidPath($out->path);
|
|
|
|
|
}
|
2013-02-13 16:49:28 +00:00
|
|
|
|
}
|
2012-06-25 15:05:16 +02:00
|
|
|
|
|
2013-02-13 16:49:28 +00:00
|
|
|
|
$c->stash->{channelBuilds} = $c->model('DB::Builds')->search(
|
|
|
|
|
{ id => $build->id },
|
|
|
|
|
{ join => ["buildoutputs"]
|
|
|
|
|
, '+select' => ['buildoutputs.path', 'buildoutputs.name'], '+as' => ['outpath', 'outname'] });
|
2009-02-25 14:34:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2013-06-17 12:34:21 -04:00
|
|
|
|
sub restart : Chained('buildChain') PathPart Args(0) {
|
2009-03-02 16:03:41 +00:00
|
|
|
|
my ($self, $c) = @_;
|
|
|
|
|
my $build = $c->stash->{build};
|
2018-10-04 21:59:42 +02:00
|
|
|
|
requireRestartPrivileges($c, $build->project);
|
2021-10-21 09:34:06 -04:00
|
|
|
|
my $n = restartBuilds($c->model('DB')->schema, $c->model('DB::Builds')->search_rs({ id => $build->id }));
|
2013-10-04 17:01:47 +02:00
|
|
|
|
error($c, "This build cannot be restarted.") if $n != 1;
|
2013-10-04 15:40:43 +02:00
|
|
|
|
$c->flash->{successMsg} = "Build has been restarted.";
|
2013-06-17 12:34:21 -04:00
|
|
|
|
$c->res->redirect($c->uri_for($self->action_for("build"), $c->req->captures));
|
2009-03-06 12:49:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2013-06-17 12:34:21 -04:00
|
|
|
|
sub cancel : Chained('buildChain') PathPart Args(0) {
|
2009-03-06 12:49:01 +00:00
|
|
|
|
my ($self, $c) = @_;
|
|
|
|
|
my $build = $c->stash->{build};
|
2019-11-05 19:29:36 +01:00
|
|
|
|
requireCancelBuildPrivileges($c, $build->project);
|
2021-10-21 09:34:06 -04:00
|
|
|
|
my $n = cancelBuilds($c->model('DB')->schema, $c->model('DB::Builds')->search_rs({ id => $build->id }));
|
2013-10-04 15:40:43 +02:00
|
|
|
|
error($c, "This build cannot be cancelled.") if $n != 1;
|
|
|
|
|
$c->flash->{successMsg} = "Build has been cancelled.";
|
2013-06-17 12:34:21 -04:00
|
|
|
|
$c->res->redirect($c->uri_for($self->action_for("build"), $c->req->captures));
|
2009-03-02 16:03:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2013-06-17 12:34:21 -04:00
|
|
|
|
sub keep : Chained('buildChain') PathPart Args(1) {
|
2013-02-13 16:49:28 +00:00
|
|
|
|
my ($self, $c, $x) = @_;
|
|
|
|
|
my $keep = $x eq "1" ? 1 : 0;
|
2009-03-14 23:56:57 +00:00
|
|
|
|
|
|
|
|
|
my $build = $c->stash->{build};
|
|
|
|
|
|
|
|
|
|
requireProjectOwner($c, $build->project);
|
|
|
|
|
|
2013-02-13 16:49:28 +00:00
|
|
|
|
if ($keep) {
|
|
|
|
|
registerRoot $_->path foreach $build->buildoutputs;
|
|
|
|
|
}
|
2009-03-16 10:57:44 +00:00
|
|
|
|
|
2020-04-10 18:13:36 +02:00
|
|
|
|
$c->model('DB')->schema->txn_do(sub {
|
2013-02-13 16:49:28 +00:00
|
|
|
|
$build->update({keep => $keep});
|
2009-03-14 23:56:57 +00:00
|
|
|
|
});
|
|
|
|
|
|
2013-10-04 15:40:43 +02:00
|
|
|
|
$c->flash->{successMsg} =
|
2013-02-13 16:49:28 +00:00
|
|
|
|
$keep ? "Build will be kept." : "Build will not be kept.";
|
2012-06-25 15:05:16 +02:00
|
|
|
|
|
2013-06-17 12:34:21 -04:00
|
|
|
|
$c->res->redirect($c->uri_for($self->action_for("build"), $c->req->captures));
|
2009-03-14 23:56:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2015-08-10 16:18:06 +02:00
|
|
|
|
sub bump : Chained('buildChain') PathPart('bump') {
|
|
|
|
|
my ($self, $c, $x) = @_;
|
|
|
|
|
|
|
|
|
|
my $build = $c->stash->{build};
|
|
|
|
|
|
2019-11-05 19:24:51 +01:00
|
|
|
|
requireBumpPrivileges($c, $build->project);
|
2015-08-10 16:18:06 +02:00
|
|
|
|
|
|
|
|
|
$c->model('DB')->schema->txn_do(sub {
|
|
|
|
|
$build->update({globalpriority => time()});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
$c->flash->{successMsg} = "Build has been bumped to the front of the queue.";
|
|
|
|
|
|
|
|
|
|
$c->res->redirect($c->uri_for($self->action_for("build"), $c->req->captures));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2013-10-29 19:53:11 +01:00
|
|
|
|
sub get_info : Chained('buildChain') PathPart('api/get-info') Args(0) {
|
|
|
|
|
my ($self, $c) = @_;
|
|
|
|
|
my $build = $c->stash->{build};
|
|
|
|
|
$c->stash->{json}->{buildId} = $build->id;
|
|
|
|
|
$c->stash->{json}->{drvPath} = $build->drvpath;
|
|
|
|
|
my $out = getMainOutput($build);
|
|
|
|
|
$c->stash->{json}->{outPath} = $out->path if defined $out;
|
|
|
|
|
$c->forward('View::JSON');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2013-06-17 12:34:21 -04:00
|
|
|
|
sub evals : Chained('buildChain') PathPart('evals') Args(0) {
|
2013-02-21 18:49:57 +01:00
|
|
|
|
my ($self, $c) = @_;
|
|
|
|
|
|
|
|
|
|
$c->stash->{template} = 'evals.tt';
|
|
|
|
|
|
|
|
|
|
my $page = int($c->req->param('page') || "1") || 1;
|
|
|
|
|
|
|
|
|
|
my $resultsPerPage = 20;
|
|
|
|
|
|
|
|
|
|
my $evals = $c->stash->{build}->jobsetevals;
|
|
|
|
|
|
|
|
|
|
$c->stash->{page} = $page;
|
|
|
|
|
$c->stash->{resultsPerPage} = $resultsPerPage;
|
|
|
|
|
$c->stash->{total} = $evals->search({hasnewbuilds => 1})->count;
|
2021-06-16 12:42:25 -04:00
|
|
|
|
$c->stash->{evals} = getEvals($c, $evals, ($page - 1) * $resultsPerPage, $resultsPerPage)
|
2013-02-21 18:49:57 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2013-08-13 01:43:50 +02:00
|
|
|
|
# Redirect to the latest finished evaluation that contains this build.
|
|
|
|
|
sub eval : Chained('buildChain') PathPart('eval') {
|
|
|
|
|
my ($self, $c, @rest) = @_;
|
|
|
|
|
|
|
|
|
|
my $eval = $c->stash->{build}->jobsetevals->find(
|
|
|
|
|
{ hasnewbuilds => 1 },
|
|
|
|
|
{ order_by => "id DESC", rows => 1
|
|
|
|
|
, "not exists (select 1 from jobsetevalmembers m2 join builds b2 on me.eval = m2.eval and m2.build = b2.id and b2.finished = 0)"
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
notFound($c, "There is no finished evaluation containing this build.") unless defined $eval;
|
|
|
|
|
|
|
|
|
|
$c->res->redirect($c->uri_for($c->controller('JobsetEval')->action_for("view"), [$eval->id], @rest, $c->req->params));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2013-06-17 12:34:21 -04:00
|
|
|
|
sub reproduce : Chained('buildChain') PathPart('reproduce') Args(0) {
|
2013-04-04 17:30:07 +02:00
|
|
|
|
my ($self, $c) = @_;
|
|
|
|
|
$c->response->content_type('text/x-shellscript');
|
|
|
|
|
$c->response->header('Content-Disposition', 'attachment; filename="reproduce-build-' . $c->stash->{build}->id . '.sh"');
|
|
|
|
|
$c->stash->{template} = 'reproduce.tt';
|
2015-05-26 15:54:38 +02:00
|
|
|
|
$c->stash->{eval} = getFirstEval($c->stash->{build});
|
2013-04-04 17:30:07 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2009-02-25 12:03:13 +00:00
|
|
|
|
1;
|