Merge branch 'custom-channels' of https://github.com/aszlig/hydra

This commit is contained in:
Eelco Dolstra 2015-10-16 17:00:29 +02:00
commit 30823078c4
17 changed files with 233 additions and 50 deletions

@ -129,6 +129,7 @@ static void findJobsWrapped(EvalState & state, JSONObject & top,
res.attr("schedulingPriority", drv.queryMetaInt("schedulingPriority", 100)); res.attr("schedulingPriority", drv.queryMetaInt("schedulingPriority", 100));
res.attr("timeout", drv.queryMetaInt("timeout", 36000)); res.attr("timeout", drv.queryMetaInt("timeout", 36000));
res.attr("maxSilent", drv.queryMetaInt("maxSilent", 7200)); res.attr("maxSilent", drv.queryMetaInt("maxSilent", 7200));
res.attr("isChannel", drv.queryMetaBool("isHydraChannel", false));
/* If this is an aggregate, then get its constituents. */ /* If this is an aggregate, then get its constituents. */
Bindings::iterator a = v.attrs->find(state.symbols.create("_hydraAggregate")); Bindings::iterator a = v.attrs->find(state.symbols.create("_hydraAggregate"));

@ -32,23 +32,16 @@ sub all : Chained('get_builds') PathPart {
} }
sub nix : Chained('get_builds') PathPart('channel') CaptureArgs(1) { sub nix : Chained('get_builds') PathPart('channel/latest') CaptureArgs(1) {
my ($self, $c, $channelName) = @_; my ($self, $c, $channelName) = @_;
eval {
if ($channelName eq "latest") { $c->stash->{channelName} = $c->stash->{channelBaseName} . "-latest";
$c->stash->{channelName} = $c->stash->{channelBaseName} . "-latest"; $c->stash->{channelBuilds} = $c->stash->{latestSucceeded}
$c->stash->{channelBuilds} = $c->stash->{latestSucceeded} ->search_literal("exists (select 1 from buildproducts where build = me.id and type = 'nix-build')")
->search_literal("exists (select 1 from buildproducts where build = me.id and type = 'nix-build')") ->search({}, { columns => [@buildListColumns, 'drvpath', 'description', 'homepage']
->search({}, { columns => [@buildListColumns, 'drvpath', 'description', 'homepage'] , join => ["buildoutputs"]
, join => ["buildoutputs"] , order_by => ["me.id", "buildoutputs.name"]
, order_by => ["me.id", "buildoutputs.name"] , '+select' => ['buildoutputs.path', 'buildoutputs.name'], '+as' => ['outpath', 'outname'] });
, '+select' => ['buildoutputs.path', 'buildoutputs.name'], '+as' => ['outpath', 'outname'] });
}
else {
notFound($c, "Unknown channel `$channelName'.");
}
};
error($c, $@) if $@;
} }

@ -130,6 +130,7 @@ sub channel_contents : Chained('nix') PathPart('') Args(0) {
# garbage-collected. That should be true for the "latest" # garbage-collected. That should be true for the "latest"
# channel. # channel.
getChannelData($c, 0); getChannelData($c, 0);
$c->stash->{genericChannel} = 1;
$c->stash->{template} = 'channel-contents.tt'; $c->stash->{template} = 'channel-contents.tt';
$c->stash->{nixPkgs} = [sortPkgs @{$c->stash->{nixPkgs}}]; $c->stash->{nixPkgs} = [sortPkgs @{$c->stash->{nixPkgs}}];
} }

@ -0,0 +1,83 @@
package Hydra::Controller::Channel;
use strict;
use warnings;
use base 'Hydra::Base::Controller::REST';
sub channel : Chained('/') PathPart('channel/custom') CaptureArgs(3) {
my ($self, $c, $projectName, $jobsetName, $channelName) = @_;
$c->stash->{project} = $c->model('DB::Projects')->find($projectName);
notFound($c, "Project $projectName doesn't exist.")
if !$c->stash->{project};
$c->stash->{jobset} = $c->stash->{project}->jobsets->find({
name => $jobsetName
});
notFound($c, "Jobset $jobsetName doesn't exist.")
if !$c->stash->{jobset};
my $lastSuccessful = $c->model('DB::Builds')->find(
{ 'eval.hasnewbuilds' => 1
, project => $projectName
, jobset => $jobsetName
, job => $channelName
, buildstatus => 0
},
{ rows => 1, order_by => "eval.id desc"
, join => { jobsetevalmembers => 'eval' }
}
);
notFound($c, "Channel $channelName either doesn't exist ".
"or was never built successfully.")
if !$lastSuccessful;
$c->stash->{lastSuccessful} = $lastSuccessful;
}
sub overview : Chained('channel') PathPart('') Args(0) {
my ($self, $c) = @_;
$c->stash->{constituents} = [
$c->stash->{lastSuccessful}->constituents_->search(
{}, {order_by => ["job"]}
)
];
$c->stash->{genericChannel} = 0;
$c->stash->{template} = 'channel-contents.tt';
}
sub nixexprs : Chained('channel') PathPart('') Args(1) {
my ($self, $c, $productName) = @_;
my $product = $c->stash->{lastSuccessful}->buildproducts->find(
{ type => "channel", name => $productName }
);
my $url = $c->uri_for(
$c->controller("Build")->action_for("download"),
[$c->stash->{lastSuccessful}->id],
$product->productnr,
$productName
);
$c->res->redirect($url);
}
sub binary_cache_url : Chained('channel') PathPart('binary-cache-url') Args(0) {
my ($self, $c) = @_;
$c->stash->{'plain'} = { data => $c->uri_for('/') };
$c->response->content_type('text/plain');
$c->forward('Hydra::View::Plain');
}
1;

@ -108,43 +108,43 @@ sub jobs_tab : Chained('jobsetChain') PathPart('jobs-tab') Args(0) {
$c->stash->{filter} = $c->request->params->{filter} // ""; $c->stash->{filter} = $c->request->params->{filter} // "";
my $filter = "%" . $c->stash->{filter} . "%"; my $filter = "%" . $c->stash->{filter} . "%";
my @evals = $c->stash->{jobset}->jobsetevals->search({ hasnewbuilds => 1}, { order_by => "id desc", rows => 20 }); my ($evals, $builds) = searchBuildsAndEvalsForJobset(
$c->stash->{jobset},
my $evals = {}; { job => { ilike => $filter }, ischannel => 0 },
my %jobs; 10000
my $nrBuilds = 0; );
foreach my $eval (@evals) {
my @builds = $eval->builds->search(
{ job => { ilike => $filter } },
{ columns => ['id', 'job', 'finished', 'buildstatus'] });
foreach my $b (@builds) {
my $jobName = $b->get_column('job');
$evals->{$eval->id}->{timestamp} = $eval->timestamp;
$evals->{$eval->id}->{jobs}->{$jobName} =
{ id => $b->id, finished => $b->finished, buildstatus => $b->buildstatus };
$jobs{$jobName} = 1;
$nrBuilds++;
}
last if $nrBuilds >= 10000;
}
if ($c->request->params->{showInactive}) { if ($c->request->params->{showInactive}) {
$c->stash->{showInactive} = 1; $c->stash->{showInactive} = 1;
foreach my $job ($c->stash->{jobset}->jobs->search({ name => { ilike => $filter } })) { foreach my $job ($c->stash->{jobset}->jobs->search({ name => { ilike => $filter } })) {
next if defined $jobs{$job->name}; next if defined $builds->{$job->name};
$c->stash->{inactiveJobs}->{$job->name} = $jobs{$job->name} = 1; $c->stash->{inactiveJobs}->{$job->name} = $builds->{$job->name} = 1;
} }
} }
$c->stash->{evals} = $evals; $c->stash->{evals} = $evals;
my @jobs = sort (keys %jobs); my @jobs = sort (keys %$builds);
$c->stash->{nrJobs} = scalar @jobs; $c->stash->{nrJobs} = scalar @jobs;
splice @jobs, 250 if $c->stash->{filter} eq ""; splice @jobs, 250 if $c->stash->{filter} eq "";
$c->stash->{jobs} = [@jobs]; $c->stash->{jobs} = [@jobs];
} }
sub channels_tab : Chained('jobsetChain') PathPart('channels-tab') Args(0) {
my ($self, $c) = @_;
$c->stash->{template} = 'jobset-channels-tab.tt';
my ($evals, $builds) = searchBuildsAndEvalsForJobset(
$c->stash->{jobset},
{ ischannel => 1 }
);
$c->stash->{evals} = $evals;
my @channels = sort (keys %$builds);
$c->stash->{channels} = [@channels];
}
# Hydra::Base::Controller::ListBuilds needs this. # Hydra::Base::Controller::ListBuilds needs this.
sub get_builds : Chained('jobsetChain') PathPart('') CaptureArgs(0) { sub get_builds : Chained('jobsetChain') PathPart('') CaptureArgs(0) {
my ($self, $c) = @_; my ($self, $c) = @_;

@ -453,6 +453,7 @@ sub checkBuild {
, busy => 0 , busy => 0
, locker => "" , locker => ""
, iscurrent => 1 , iscurrent => 1
, ischannel => $buildInfo->{isChannel}
}); });
$build->buildoutputs->create({ name => $_, path => $buildInfo->{outputs}->{$_} }) $build->buildoutputs->create({ name => $_, path => $buildInfo->{outputs}->{$_} })

@ -10,6 +10,7 @@ use Hydra::Helper::Nix;
our @ISA = qw(Exporter); our @ISA = qw(Exporter);
our @EXPORT = qw( our @EXPORT = qw(
getBuild getPreviousBuild getNextBuild getPreviousSuccessfulBuild getBuild getPreviousBuild getNextBuild getPreviousSuccessfulBuild
searchBuildsAndEvalsForJobset
error notFound gone accessDenied error notFound gone accessDenied
forceLogin requireUser requireProjectOwner requireAdmin requirePost isAdmin isProjectOwner forceLogin requireUser requireProjectOwner requireAdmin requirePost isAdmin isProjectOwner
trim trim
@ -85,6 +86,44 @@ sub getPreviousSuccessfulBuild {
} }
sub searchBuildsAndEvalsForJobset {
my ($jobset, $condition, $maxBuilds) = @_;
my @evals = $jobset->jobsetevals->search(
{ hasnewbuilds => 1},
{ order_by => "id desc",
rows => 20
});
my $evals = {};
my %builds;
my $nrBuilds = 0;
foreach my $eval (@evals) {
my @allBuilds = $eval->builds->search(
$condition,
{ columns => ['id', 'job', 'finished', 'buildstatus'] }
);
foreach my $b (@allBuilds) {
my $jobName = $b->get_column('job');
$evals->{$eval->id}->{timestamp} = $eval->timestamp;
$evals->{$eval->id}->{builds}->{$jobName} = {
id => $b->id,
finished => $b->finished,
buildstatus => $b->buildstatus
};
$builds{$jobName} = 1;
$nrBuilds++;
}
last if $maxBuilds && $nrBuilds >= $maxBuilds;
}
return ($evals, \%builds);
}
sub error { sub error {
my ($c, $msg, $status) = @_; my ($c, $msg, $status) = @_;
$c->response->status($status) if defined $status; $c->response->status($status) if defined $status;

@ -116,6 +116,12 @@ __PACKAGE__->table("Builds");
default_value: 36000 default_value: 36000
is_nullable: 1 is_nullable: 1
=head2 ischannel
data_type: 'integer'
default_value: 0
is_nullable: 1
=head2 iscurrent =head2 iscurrent
data_type: 'integer' data_type: 'integer'
@ -239,6 +245,8 @@ __PACKAGE__->add_columns(
{ data_type => "integer", default_value => 3600, is_nullable => 1 }, { data_type => "integer", default_value => 3600, is_nullable => 1 },
"timeout", "timeout",
{ data_type => "integer", default_value => 36000, is_nullable => 1 }, { data_type => "integer", default_value => 36000, is_nullable => 1 },
"ischannel",
{ data_type => "integer", default_value => 0, is_nullable => 1 },
"iscurrent", "iscurrent",
{ data_type => "integer", default_value => 0, is_nullable => 1 }, { data_type => "integer", default_value => 0, is_nullable => 1 },
"nixexprinput", "nixexprinput",
@ -558,8 +566,8 @@ __PACKAGE__->many_to_many(
); );
# Created by DBIx::Class::Schema::Loader v0.07043 @ 2015-08-10 15:10:41 # Created by DBIx::Class::Schema::Loader v0.07043 @ 2015-09-10 17:34:23
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:rjifgnPtjY96MaQ7eiGzaA # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:JRelp13Cyfi+QVxC92xuqQ
__PACKAGE__->has_many( __PACKAGE__->has_many(
"dependents", "dependents",

@ -190,7 +190,7 @@
<td>[% IF cachedBuild; INCLUDE renderFullBuildLink build=cachedBuild; ELSE %]<em>unknown</em>[% END %]</td> <td>[% IF cachedBuild; INCLUDE renderFullBuildLink build=cachedBuild; ELSE %]<em>unknown</em>[% END %]</td>
</tr> </tr>
[% END %] [% END %]
[% IF !isAggregate && build.finished; actualBuild = build.iscachedbuild ? cachedBuild : build %] [% IF (!isAggregate || !build.ischannel) && build.finished; actualBuild = build.iscachedbuild ? cachedBuild : build %]
[% IF actualBuild %] [% IF actualBuild %]
<tr> <tr>
<th>Duration:</th> <th>Duration:</th>
@ -202,7 +202,7 @@
<td>[% INCLUDE renderDateTime timestamp = build.stoptime; %]</td> <td>[% INCLUDE renderDateTime timestamp = build.stoptime; %]</td>
</tr> </tr>
[% END %] [% END %]
[% IF !isAggregate && buildLogExists(build) %] [% IF (!isAggregate || !build.ischannel) && buildLogExists(build) %]
<tr> <tr>
<th>Logfile:</th> <th>Logfile:</th>
<td> <td>
@ -217,7 +217,7 @@
</tr> </tr>
</table> </table>
[% IF build.buildproducts && !isAggregate %] [% IF build.ischannel || (build.buildproducts && !isAggregate) %]
<h3>Build products</h3> <h3>Build products</h3>

@ -22,12 +22,13 @@ $ nix-env -i foo</pre>
$ nix-channel --update $ nix-channel --update
$ nix-env -u '*'</pre> $ nix-env -u '*'</pre>
[% IF genericChannel %]
<p>Alternatively, if you have associated the <p>Alternatively, if you have associated the
<tt>application/nix-package</tt> MIME type with the <tt>application/nix-package</tt> MIME type with the
<tt>nix-install-package</tt> program in your web browser, you can <tt>nix-install-package</tt> program in your web browser, you can
install the package simply by clicking on the packages below.</p> install the package simply by clicking on the packages below.</p>
<h2>Packages</h2> <h2>Packages</h2>
<p>This channel contains the following packages.</p> <p>This channel contains the following packages.</p>
@ -69,5 +70,17 @@ install the package simply by clicking on the packages below.</p>
</table> </table>
[% ELSE %]
[% PROCESS "product-list.tt" %]
<h2>Contents</h2>
[% INCLUDE renderProductList build=lastSuccessful %]
<p>Upgrades depend on the success/failure of the following constituents:</p>
[% INCLUDE renderBuildList builds=constituents %]
[% END %]
[% END %] [% END %]

@ -0,0 +1,34 @@
[% PROCESS common.tt %]
[% IF channels.size == 0 %]
<div class="alert">There are no channels available.</div>
[% ELSE %]
[% evalIds = evals.keys.nsort.reverse %]
<table class="table table-striped table-condensed table-header-rotated">
<thead>
<tr>
<th style="width: 1em;">Channel</th>
[% FOREACH eval IN evalIds %]
<th class="rotate-45">
<div><span>
<a href="[% c.uri_for('/eval' eval) %]">[% INCLUDE renderRelativeDate timestamp=evals.$eval.timestamp %]</a>
</span></div></th>
[% END %]
</tr>
</thead>
<tbody>
[% FOREACH chan IN channels-%]
<tr>
<th><span><a href="[% c.uri_for('/channel/custom' project.name jobset.name chan) %]">[% chan %]</a></span></th>
[% FOREACH eval IN evalIds %]
<td>[% r = evals.$eval.builds.$chan; IF r.id %]<a href="[% c.uri_for('/build' r.id) %]">[% INCLUDE renderBuildStatusIcon size=16 build=r %]</a>[% END %]</td>
[% END %]
</tr>
[% END %]
</tbody>
</table>
[% END %]

@ -59,7 +59,7 @@
<tr> <tr>
<th><span [% IF inactiveJobs.$j %]class="muted override-link"[% END %]>[% INCLUDE renderJobName project=project.name jobset=jobset.name job=j %]</span></th> <th><span [% IF inactiveJobs.$j %]class="muted override-link"[% END %]>[% INCLUDE renderJobName project=project.name jobset=jobset.name job=j %]</span></th>
[% FOREACH eval IN evalIds %] [% FOREACH eval IN evalIds %]
<td>[% r = evals.$eval.jobs.$j; IF r.id %]<a href="[% c.uri_for('/build' r.id) %]">[% INCLUDE renderBuildStatusIcon size=16 build=r %]</a>[% END %]</td> <td>[% r = evals.$eval.builds.$j; IF r.id %]<a href="[% c.uri_for('/build' r.id) %]">[% INCLUDE renderBuildStatusIcon size=16 build=r %]</a>[% END %]</td>
[% END %] [% END %]
</tr> </tr>
[% END %] [% END %]

@ -64,6 +64,7 @@
<li><a href="#tabs-jobs" data-toggle="tab">Jobs</a></li> <li><a href="#tabs-jobs" data-toggle="tab">Jobs</a></li>
<li><a href="#tabs-configuration" data-toggle="tab">Configuration</a></li> <li><a href="#tabs-configuration" data-toggle="tab">Configuration</a></li>
<li><a href="#tabs-links" data-toggle="tab">Links</a></li> <li><a href="#tabs-links" data-toggle="tab">Links</a></li>
<li><a href="#tabs-channels" data-toggle="tab">Channels</a></li>
</ul> </ul>
<div id="generic-tabs" class="tab-content"> <div id="generic-tabs" class="tab-content">
@ -165,6 +166,8 @@
</ul> </ul>
</div> </div>
[% INCLUDE makeLazyTab tabName="tabs-channels" uri=c.uri_for('/jobset' project.name jobset.name "channels-tab") %]
</div> </div>
<script> <script>

@ -115,10 +115,9 @@
</td> </td>
</tr> </tr>
[% END %]
[% END %] [% CASE ["file", "channel"] %]
[% CASE "file" %]
<tr class="product"> <tr class="product">
<td> <td>
@ -137,7 +136,13 @@
[% CASE "binary-dist" %] [% CASE "binary-dist" %]
<img src="[% c.uri_for("/static/images/binary-dist.png") %]" alt="Binary distribution" /> Binary distribution <tt>[% product.name %]</tt> <img src="[% c.uri_for("/static/images/binary-dist.png") %]" alt="Binary distribution" /> Binary distribution <tt>[% product.name %]</tt>
[% CASE DEFAULT %] [% CASE DEFAULT %]
File <tt>[% product.name %]</tt> of type <tt>[% product.subtype %]</tt> [% IF product.type == "channel" %]
<img src="[% c.uri_for("/static/images/channel.png") %]" alt="Channel" />
Channel expression tarball <tt>[% product.name %]</tt>
[% IF product.subtype != "-" %]for <tt>[% product.subtype %]</tt>[% END %]
[% ELSE %]
File <tt>[% product.name %]</tt> of type <tt>[% product.subtype %]</tt>
[% END %]
[% END %] [% END %]
</a> </a>
[% WRAPPER makePopover title="Details" classes="btn-mini" %] [% WRAPPER makePopover title="Details" classes="btn-mini" %]

Binary file not shown.

After

(image error) Size: 1.6 KiB

@ -157,6 +157,7 @@ create table Builds (
maxsilent integer default 3600, -- meta.maxsilent maxsilent integer default 3600, -- meta.maxsilent
timeout integer default 36000, -- meta.timeout timeout integer default 36000, -- meta.timeout
isChannel integer not null default 0, -- meta.isHydraChannel
isCurrent integer default 0, isCurrent integer default 0,
-- Copy of the nixExprInput/nixExprPath fields of the jobset that -- Copy of the nixExprInput/nixExprPath fields of the jobset that

1
src/sql/upgrade-42.sql Normal file

@ -0,0 +1 @@
alter table Builds add column isChannel integer not null default 0;