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

View file

@ -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"));

View file

@ -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 $@;
} }

View file

@ -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}}];
} }

View file

@ -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;

View file

@ -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) = @_;

View file

@ -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}->{$_} })

View file

@ -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;

View file

@ -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",

View file

@ -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>

View file

@ -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 %]

View file

@ -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 %]

View file

@ -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 %]

View file

@ -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>

View file

@ -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

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -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
View file

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