Merge in the first bits of the API work
The catalyst-action-rest branch from shlevy/hydra was an exploration of using Catalyst::Action::REST to create a JSON API for hydra. This commit merges in the best bits from that experiment, with the goal that further API endpoints can be added incrementally. In addition to migrating more endpoints, there is potential for improvement in what's already been done: * The web interface can be updated to use the same non-GET endpoints as the JSON interface (using x-tunneled-method) instead of having a separate endpoint * The web rendering should use the $c->stash->{resource} data structure where applicable rather than putting the same data in two places in the stash * Which columns to render for each endpoint is a completely debatable question * Hydra::Component::ToJSON should turn has_many relations that have strings as their primary keys into objects instead of arrays Fixes NixOS/hydra#98 Signed-off-by: Shea Levy <shea@shealevy.com>
This commit is contained in:
parent
d18fc4fc38
commit
002ac9ef63
37
release.nix
37
release.nix
|
@ -71,6 +71,7 @@ in rec {
|
|||
CatalystViewJSON
|
||||
CatalystViewTT
|
||||
CatalystXScriptServerStarman
|
||||
CatalystActionREST
|
||||
CryptRandPasswd
|
||||
DBDPg
|
||||
DBDSQLite
|
||||
|
@ -167,4 +168,40 @@ in rec {
|
|||
'';
|
||||
});
|
||||
|
||||
tests.api = genAttrs' (system:
|
||||
with import <nixos/lib/testing.nix> { inherit system; };
|
||||
let hydra = builtins.getAttr system build; in # build.${system}
|
||||
simpleTest {
|
||||
machine =
|
||||
{ config, pkgs, ... }:
|
||||
{ services.postgresql.enable = true;
|
||||
services.postgresql.package = pkgs.postgresql92;
|
||||
environment.systemPackages = [ hydra pkgs.perlPackages.LWP pkgs.perlPackages.JSON ];
|
||||
virtualisation.memorySize = 2048;
|
||||
};
|
||||
|
||||
testScript =
|
||||
''
|
||||
$machine->waitForJob("postgresql");
|
||||
|
||||
# Initialise the database and the state.
|
||||
$machine->mustSucceed
|
||||
( "createdb -O root hydra"
|
||||
, "psql hydra -f ${hydra}/libexec/hydra/sql/hydra-postgresql.sql"
|
||||
, "mkdir /var/lib/hydra"
|
||||
, "echo \"insert into Users(userName, emailAddress, password) values('root', 'e.dolstra\@tudelft.nl', '\$(echo -n foobar | sha1sum | cut -c1-40)');\" | psql hydra"
|
||||
, "echo \"insert into UserRoles(userName, role) values('root', 'admin');\" | psql hydra"
|
||||
, "mkdir /run/jobset"
|
||||
, "chmod 755 /run/jobset"
|
||||
, "cp ${./tests/api-test.nix} /run/jobset/default.nix"
|
||||
, "chmod 644 /run/jobset/default.nix"
|
||||
);
|
||||
|
||||
# Start the web interface.
|
||||
$machine->mustSucceed("NIX_STORE_DIR=/run/nix NIX_LOG_DIR=/run/nix/var/log/nix NIX_STATE_DIR=/run/nix/var/nix HYDRA_DATA=/var/lib/hydra HYDRA_DBI='dbi:Pg:dbname=hydra;user=root;' LOGNAME=root DBIC_TRACE=1 hydra-server -d >&2 &");
|
||||
$machine->waitForOpenPort("3000");
|
||||
|
||||
$machine->mustSucceed("perl ${./tests/api-test.pl} >&2");
|
||||
'';
|
||||
});
|
||||
}
|
||||
|
|
|
@ -101,7 +101,7 @@ sub latest : Chained('get_builds') PathPart('latest') {
|
|||
|
||||
notFound($c, "There is no successful build to redirect to.") unless defined $latest;
|
||||
|
||||
$c->res->redirect($c->uri_for($c->controller('Build')->action_for("view_build"), [$latest->id], @rest));
|
||||
$c->res->redirect($c->uri_for($c->controller('Build')->action_for("build"), [$latest->id], @rest));
|
||||
}
|
||||
|
||||
|
||||
|
@ -116,7 +116,7 @@ sub latest_for : Chained('get_builds') PathPart('latest-for') {
|
|||
|
||||
notFound($c, "There is no successful build for platform `$system' to redirect to.") unless defined $latest;
|
||||
|
||||
$c->res->redirect($c->uri_for($c->controller('Build')->action_for("view_build"), [$latest->id], @rest));
|
||||
$c->res->redirect($c->uri_for($c->controller('Build')->action_for("build"), [$latest->id], @rest));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ package Hydra::Base::Controller::NixChannel;
|
|||
|
||||
use strict;
|
||||
use warnings;
|
||||
use base 'Catalyst::Controller';
|
||||
use base 'Hydra::Base::Controller::REST';
|
||||
use List::MoreUtils qw(all);
|
||||
use Nix::Store;
|
||||
use Hydra::Helper::Nix;
|
||||
|
|
18
src/lib/Hydra/Base/Controller/REST.pm
Normal file
18
src/lib/Hydra/Base/Controller/REST.pm
Normal file
|
@ -0,0 +1,18 @@
|
|||
package Hydra::Base::Controller::REST;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use base 'Catalyst::Controller::REST';
|
||||
|
||||
__PACKAGE__->config(
|
||||
map => {
|
||||
'text/html' => [ 'View', 'TT' ]
|
||||
},
|
||||
default => 'text/html',
|
||||
'stash_key' => 'resource',
|
||||
);
|
||||
|
||||
sub begin { my ( $self, $c ) = @_; $c->forward('Hydra::Controller::Root::begin'); }
|
||||
sub end { my ( $self, $c ) = @_; $c->forward('Hydra::Controller::Root::end'); }
|
||||
|
||||
1;
|
43
src/lib/Hydra/Component/ToJSON.pm
Normal file
43
src/lib/Hydra/Component/ToJSON.pm
Normal file
|
@ -0,0 +1,43 @@
|
|||
use utf8;
|
||||
package Hydra::Component::ToJSON;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class';
|
||||
|
||||
sub TO_JSON {
|
||||
my $self = shift;
|
||||
my $json = { $self->get_columns };
|
||||
my $rs = $self->result_source;
|
||||
my @relnames = $rs->relationships;
|
||||
RELLOOP: foreach my $relname (@relnames) {
|
||||
my $relinfo = $rs->relationship_info($relname);
|
||||
next unless defined $relinfo->{attrs}->{accessor};
|
||||
my $accessor = $relinfo->{attrs}->{accessor};
|
||||
if ($accessor eq "single" and exists $self->{_relationship_data}{$relname}) {
|
||||
$json->{$relname} = $self->$relname->TO_JSON;
|
||||
} else {
|
||||
unless (defined $self->{related_resultsets}{$relname}) {
|
||||
my $cond = $relinfo->{cond};
|
||||
if (ref $cond eq 'HASH') {
|
||||
foreach my $k (keys %{$cond}) {
|
||||
my $v = $cond->{$k};
|
||||
$v =~ s/^self\.//;
|
||||
next RELLOOP unless $self->has_column_loaded($v);
|
||||
}
|
||||
} #!!! TODO: Handle ARRAY conditions
|
||||
}
|
||||
if (defined $self->related_resultset($relname)->get_cache) {
|
||||
if ($accessor eq "multi") {
|
||||
$json->{$relname} = [ map { $_->TO_JSON } $self->$relname ];
|
||||
} else {
|
||||
$json->{$relname} = $self->$relname->TO_JSON;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $json;
|
||||
}
|
||||
|
||||
1;
|
|
@ -3,7 +3,7 @@ package Hydra::Controller::API;
|
|||
use utf8;
|
||||
use strict;
|
||||
use warnings;
|
||||
use base 'Catalyst::Controller';
|
||||
use base 'Hydra::Base::Controller::REST';
|
||||
use Hydra::Helper::Nix;
|
||||
use Hydra::Helper::AddBuilds;
|
||||
use Hydra::Helper::CatalystUtils;
|
||||
|
@ -310,6 +310,11 @@ sub push : Chained('api') PathPart('push') Args(0) {
|
|||
, where => \ [ 'exists (select 1 from JobsetInputAlts where project = me.project and jobset = me.name and value = ?)', [ 'value', $r ] ]
|
||||
});
|
||||
}
|
||||
|
||||
$self->status_ok(
|
||||
$c,
|
||||
entity => { jobsetsTriggered => $c->stash->{json}->{jobsetsTriggered} }
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ use Nix::Config;
|
|||
use List::MoreUtils qw(all);
|
||||
|
||||
|
||||
sub build : Chained('/') PathPart CaptureArgs(1) {
|
||||
sub buildChain :Chained('/') :PathPart('build') :CaptureArgs(1) {
|
||||
my ($self, $c, $id) = @_;
|
||||
|
||||
$c->stash->{id} = $id;
|
||||
|
@ -50,7 +50,9 @@ sub findBuildStepByDrvPath {
|
|||
}
|
||||
|
||||
|
||||
sub view_build : Chained('build') PathPart('') Args(0) {
|
||||
sub build :Chained('buildChain') :PathPart('') :Args(0) :ActionClass('REST') { }
|
||||
|
||||
sub build_GET {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
my $build = $c->stash->{build};
|
||||
|
@ -96,10 +98,26 @@ sub view_build : Chained('build') PathPart('') Args(0) {
|
|||
($c->stash->{eval}) = $c->stash->{build}->jobsetevals->search(
|
||||
{ hasnewbuilds => 1},
|
||||
{ limit => 1, order_by => ["id"] });
|
||||
$self->status_ok(
|
||||
$c,
|
||||
entity => $c->model('DB::Builds')->find($build->id,{
|
||||
columns => [
|
||||
'id',
|
||||
'finished',
|
||||
'timestamp',
|
||||
'buildstatus',
|
||||
'job',
|
||||
'project',
|
||||
'jobset',
|
||||
'starttime',
|
||||
'stoptime',
|
||||
]
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
sub view_nixlog : Chained('build') PathPart('nixlog') {
|
||||
sub view_nixlog : Chained('buildChain') PathPart('nixlog') {
|
||||
my ($self, $c, $stepnr, $mode) = @_;
|
||||
|
||||
my $step = $c->stash->{build}->buildsteps->find({stepnr => $stepnr});
|
||||
|
@ -111,7 +129,7 @@ sub view_nixlog : Chained('build') PathPart('nixlog') {
|
|||
}
|
||||
|
||||
|
||||
sub view_log : Chained('build') PathPart('log') {
|
||||
sub view_log : Chained('buildChain') PathPart('log') {
|
||||
my ($self, $c, $mode) = @_;
|
||||
showLog($c, $c->stash->{build}->drvpath, $mode);
|
||||
}
|
||||
|
@ -176,7 +194,7 @@ sub checkPath {
|
|||
}
|
||||
|
||||
|
||||
sub download : Chained('build') PathPart {
|
||||
sub download : Chained('buildChain') PathPart {
|
||||
my ($self, $c, $productnr, @path) = @_;
|
||||
|
||||
$productnr = 1 if !defined $productnr;
|
||||
|
@ -223,7 +241,7 @@ sub download : Chained('build') PathPart {
|
|||
# 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).
|
||||
sub download_by_type : Chained('build') PathPart('download-by-type') {
|
||||
sub download_by_type : Chained('buildChain') PathPart('download-by-type') {
|
||||
my ($self, $c, $type, $subtype, @path) = @_;
|
||||
|
||||
notFound($c, "You need to specify a type and a subtype in the URI.")
|
||||
|
@ -238,7 +256,7 @@ sub download_by_type : Chained('build') PathPart('download-by-type') {
|
|||
}
|
||||
|
||||
|
||||
sub contents : Chained('build') PathPart Args(1) {
|
||||
sub contents : Chained('buildChain') PathPart Args(1) {
|
||||
my ($self, $c, $productnr) = @_;
|
||||
|
||||
my $product = $c->stash->{build}->buildproducts->find({productnr => $productnr});
|
||||
|
@ -342,7 +360,7 @@ sub getDependencyGraph {
|
|||
}
|
||||
|
||||
|
||||
sub build_deps : Chained('build') PathPart('build-deps') {
|
||||
sub build_deps : Chained('buildChain') PathPart('build-deps') {
|
||||
my ($self, $c) = @_;
|
||||
my $build = $c->stash->{build};
|
||||
my $drvPath = $build->drvpath;
|
||||
|
@ -355,7 +373,7 @@ sub build_deps : Chained('build') PathPart('build-deps') {
|
|||
}
|
||||
|
||||
|
||||
sub runtime_deps : Chained('build') PathPart('runtime-deps') {
|
||||
sub runtime_deps : Chained('buildChain') PathPart('runtime-deps') {
|
||||
my ($self, $c) = @_;
|
||||
my $build = $c->stash->{build};
|
||||
my @outPaths = map { $_->path } $build->buildoutputs->all;
|
||||
|
@ -369,7 +387,7 @@ sub runtime_deps : Chained('build') PathPart('runtime-deps') {
|
|||
}
|
||||
|
||||
|
||||
sub nix : Chained('build') PathPart('nix') CaptureArgs(0) {
|
||||
sub nix : Chained('buildChain') PathPart('nix') CaptureArgs(0) {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
my $build = $c->stash->{build};
|
||||
|
@ -389,7 +407,7 @@ sub nix : Chained('build') PathPart('nix') CaptureArgs(0) {
|
|||
}
|
||||
|
||||
|
||||
sub restart : Chained('build') PathPart Args(0) {
|
||||
sub restart : Chained('buildChain') PathPart Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
my $build = $c->stash->{build};
|
||||
|
@ -404,11 +422,11 @@ sub restart : Chained('build') PathPart Args(0) {
|
|||
|
||||
$c->flash->{buildMsg} = "Build has been restarted.";
|
||||
|
||||
$c->res->redirect($c->uri_for($self->action_for("view_build"), $c->req->captures));
|
||||
$c->res->redirect($c->uri_for($self->action_for("build"), $c->req->captures));
|
||||
}
|
||||
|
||||
|
||||
sub cancel : Chained('build') PathPart Args(0) {
|
||||
sub cancel : Chained('buildChain') PathPart Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
my $build = $c->stash->{build};
|
||||
|
@ -434,11 +452,11 @@ sub cancel : Chained('build') PathPart Args(0) {
|
|||
|
||||
$c->flash->{buildMsg} = "Build has been cancelled.";
|
||||
|
||||
$c->res->redirect($c->uri_for($self->action_for("view_build"), $c->req->captures));
|
||||
$c->res->redirect($c->uri_for($self->action_for("build"), $c->req->captures));
|
||||
}
|
||||
|
||||
|
||||
sub keep : Chained('build') PathPart Args(1) {
|
||||
sub keep : Chained('buildChain') PathPart Args(1) {
|
||||
my ($self, $c, $x) = @_;
|
||||
my $keep = $x eq "1" ? 1 : 0;
|
||||
|
||||
|
@ -457,11 +475,11 @@ sub keep : Chained('build') PathPart Args(1) {
|
|||
$c->flash->{buildMsg} =
|
||||
$keep ? "Build will be kept." : "Build will not be kept.";
|
||||
|
||||
$c->res->redirect($c->uri_for($self->action_for("view_build"), $c->req->captures));
|
||||
$c->res->redirect($c->uri_for($self->action_for("build"), $c->req->captures));
|
||||
}
|
||||
|
||||
|
||||
sub add_to_release : Chained('build') PathPart('add-to-release') Args(0) {
|
||||
sub add_to_release : Chained('buildChain') PathPart('add-to-release') Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
my $build = $c->stash->{build};
|
||||
|
@ -486,11 +504,11 @@ sub add_to_release : Chained('build') PathPart('add-to-release') Args(0) {
|
|||
|
||||
$c->flash->{buildMsg} = "Build added to project <tt>$releaseName</tt>.";
|
||||
|
||||
$c->res->redirect($c->uri_for($self->action_for("view_build"), $c->req->captures));
|
||||
$c->res->redirect($c->uri_for($self->action_for("build"), $c->req->captures));
|
||||
}
|
||||
|
||||
|
||||
sub clone : Chained('build') PathPart('clone') Args(0) {
|
||||
sub clone : Chained('buildChain') PathPart('clone') Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
my $build = $c->stash->{build};
|
||||
|
@ -501,7 +519,7 @@ sub clone : Chained('build') PathPart('clone') Args(0) {
|
|||
}
|
||||
|
||||
|
||||
sub clone_submit : Chained('build') PathPart('clone/submit') Args(0) {
|
||||
sub clone_submit : Chained('buildChain') PathPart('clone/submit') Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
my $build = $c->stash->{build};
|
||||
|
@ -567,7 +585,7 @@ sub clone_submit : Chained('build') PathPart('clone/submit') Args(0) {
|
|||
}
|
||||
|
||||
|
||||
sub get_info : Chained('build') PathPart('api/get-info') Args(0) {
|
||||
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;
|
||||
|
@ -578,7 +596,7 @@ sub get_info : Chained('build') PathPart('api/get-info') Args(0) {
|
|||
}
|
||||
|
||||
|
||||
sub evals : Chained('build') PathPart('evals') Args(0) {
|
||||
sub evals : Chained('buildChain') PathPart('evals') Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
$c->stash->{template} = 'evals.tt';
|
||||
|
@ -596,7 +614,7 @@ sub evals : Chained('build') PathPart('evals') Args(0) {
|
|||
}
|
||||
|
||||
|
||||
sub reproduce : Chained('build') PathPart('reproduce') Args(0) {
|
||||
sub reproduce : Chained('buildChain') PathPart('reproduce') Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
$c->response->content_type('text/x-shellscript');
|
||||
$c->response->header('Content-Disposition', 'attachment; filename="reproduce-build-' . $c->stash->{build}->id . '.sh"');
|
||||
|
|
|
@ -7,35 +7,143 @@ use Hydra::Helper::Nix;
|
|||
use Hydra::Helper::CatalystUtils;
|
||||
|
||||
|
||||
sub jobset : Chained('/') PathPart('jobset') CaptureArgs(2) {
|
||||
sub jobsetChain :Chained('/') :PathPart('jobset') :CaptureArgs(2) {
|
||||
my ($self, $c, $projectName, $jobsetName) = @_;
|
||||
|
||||
my $project = $c->model('DB::Projects')->find($projectName)
|
||||
or notFound($c, "Project $projectName doesn't exist.");
|
||||
my $project = $c->model('DB::Projects')->find($projectName);
|
||||
|
||||
$c->stash->{project} = $project;
|
||||
if ($project) {
|
||||
$c->stash->{project} = $project;
|
||||
|
||||
$c->stash->{jobset_} = $project->jobsets->search({name => $jobsetName});
|
||||
$c->stash->{jobset} = $c->stash->{jobset_}->single
|
||||
or notFound($c, "Jobset $jobsetName doesn't exist.");
|
||||
$c->stash->{jobset_} = $project->jobsets->search({'me.name' => $jobsetName});
|
||||
my $jobset = $c->stash->{jobset_}->single;
|
||||
|
||||
if ($jobset) {
|
||||
$c->stash->{jobset} = $jobset;
|
||||
} else {
|
||||
if ($c->action->name eq "jobset" and $c->request->method eq "PUT") {
|
||||
$c->stash->{jobsetName} = $jobsetName;
|
||||
} else {
|
||||
$self->status_not_found(
|
||||
$c,
|
||||
message => "Jobset $jobsetName doesn't exist."
|
||||
);
|
||||
$c->detach;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$self->status_not_found(
|
||||
$c,
|
||||
message => "Project $projectName doesn't exist."
|
||||
);
|
||||
$c->detach;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub index : Chained('jobset') PathPart('') Args(0) {
|
||||
sub jobset :Chained('jobsetChain') :PathPart('') :Args(0) :ActionClass('REST::ForBrowsers') { }
|
||||
|
||||
sub jobset_GET {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
$c->stash->{template} = 'jobset.tt';
|
||||
|
||||
my $projectName = $c->stash->{project}->name;
|
||||
my $jobsetName = $c->stash->{jobset}->name;
|
||||
|
||||
$c->stash->{evals} = getEvals($self, $c, scalar $c->stash->{jobset}->jobsetevals, 0, 10);
|
||||
|
||||
($c->stash->{latestEval}) = $c->stash->{jobset}->jobsetevals->search({}, { limit => 1, order_by => ["id desc"] });
|
||||
|
||||
$self->status_ok(
|
||||
$c,
|
||||
entity => $c->stash->{jobset_}->find({}, {
|
||||
columns => [
|
||||
'me.name',
|
||||
'me.project',
|
||||
'me.errormsg',
|
||||
'jobsetinputs.name',
|
||||
{
|
||||
'jobsetinputs.jobsetinputalts.altnr' => 'jobsetinputalts.altnr',
|
||||
'jobsetinputs.jobsetinputalts.value' => 'jobsetinputalts.value'
|
||||
}
|
||||
],
|
||||
join => { 'jobsetinputs' => 'jobsetinputalts' },
|
||||
collapse => 1,
|
||||
order_by => "me.name"
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
sub jobset_PUT {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
requireProjectOwner($c, $c->stash->{project});
|
||||
|
||||
if (defined $c->stash->{jobset}) {
|
||||
error($c, "Cannot rename jobset `$c->stash->{params}->{oldName}' over existing jobset `$c->stash->{jobset}->name") if defined $c->stash->{params}->{oldName} and $c->stash->{params}->{oldName} ne $c->stash->{jobset}->name;
|
||||
txn_do($c->model('DB')->schema, sub {
|
||||
updateJobset($c, $c->stash->{jobset});
|
||||
});
|
||||
|
||||
if ($c->req->looks_like_browser) {
|
||||
$c->res->redirect($c->uri_for($self->action_for("jobset"),
|
||||
[$c->stash->{project}->name, $c->stash->{jobset}->name]) . "#tabs-configuration");
|
||||
} else {
|
||||
$self->status_no_content($c);
|
||||
}
|
||||
} elsif (defined $c->stash->{params}->{oldName}) {
|
||||
my $jobset = $c->stash->{project}->jobsets->find({'me.name' => $c->stash->{params}->{oldName}});
|
||||
|
||||
if (defined $jobset) {
|
||||
txn_do($c->model('DB')->schema, sub {
|
||||
updateJobset($c, $jobset);
|
||||
});
|
||||
|
||||
my $uri = $c->uri_for($self->action_for("jobset"), [$c->stash->{project}->name, $jobset->name]);
|
||||
|
||||
if ($c->req->looks_like_browser) {
|
||||
$c->res->redirect($uri . "#tabs-configuration");
|
||||
} else {
|
||||
$self->status_created(
|
||||
$c,
|
||||
location => "$uri",
|
||||
entity => { name => $jobset->name, uri => "$uri", type => "jobset" }
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$self->status_not_found(
|
||||
$c,
|
||||
message => "Jobset $c->stash->{params}->{oldName} doesn't exist."
|
||||
);
|
||||
}
|
||||
} else {
|
||||
my $exprType =
|
||||
$c->stash->{params}->{"nixexprpath"} =~ /.scm$/ ? "guile" : "nix";
|
||||
|
||||
error($c, "Invalid jobset name: ‘$c->stash->{jobsetName}’") if $c->stash->{jobsetName} !~ /^$jobsetNameRE$/;
|
||||
|
||||
my $jobset;
|
||||
txn_do($c->model('DB')->schema, sub {
|
||||
# Note: $jobsetName is validated in updateProject, which will
|
||||
# abort the transaction if the name isn't valid.
|
||||
$jobset = $c->stash->{project}->jobsets->create(
|
||||
{name => $c->stash->{jobsetName}, nixexprinput => "", nixexprpath => "", emailoverride => ""});
|
||||
updateJobset($c, $jobset);
|
||||
});
|
||||
|
||||
my $uri = $c->uri_for($self->action_for("jobset"), [$c->stash->{project}->name, $jobset->name]);
|
||||
if ($c->req->looks_like_browser) {
|
||||
$c->res->redirect($uri . "#tabs-configuration");
|
||||
} else {
|
||||
$self->status_created(
|
||||
$c,
|
||||
location => "$uri",
|
||||
entity => { name => $jobset->name, uri => "$uri", type => "jobset" }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub jobs_tab : Chained('jobset') PathPart('jobs-tab') Args(0) {
|
||||
sub jobs_tab : Chained('jobsetChain') PathPart('jobs-tab') Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
$c->stash->{template} = 'jobset-jobs-tab.tt';
|
||||
|
||||
|
@ -64,7 +172,7 @@ sub jobs_tab : Chained('jobset') PathPart('jobs-tab') Args(0) {
|
|||
}
|
||||
|
||||
|
||||
sub status_tab : Chained('jobset') PathPart('status-tab') Args(0) {
|
||||
sub status_tab : Chained('jobsetChain') PathPart('status-tab') Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
$c->stash->{template} = 'jobset-status-tab.tt';
|
||||
|
||||
|
@ -101,7 +209,7 @@ sub status_tab : Chained('jobset') PathPart('status-tab') Args(0) {
|
|||
|
||||
|
||||
# Hydra::Base::Controller::ListBuilds needs this.
|
||||
sub get_builds : Chained('jobset') PathPart('') CaptureArgs(0) {
|
||||
sub get_builds : Chained('jobsetChain') PathPart('') CaptureArgs(0) {
|
||||
my ($self, $c) = @_;
|
||||
$c->stash->{allBuilds} = $c->stash->{jobset}->builds;
|
||||
$c->stash->{jobStatus} = $c->model('DB')->resultset('JobStatusForJobset')
|
||||
|
@ -115,7 +223,7 @@ sub get_builds : Chained('jobset') PathPart('') CaptureArgs(0) {
|
|||
}
|
||||
|
||||
|
||||
sub edit : Chained('jobset') PathPart Args(0) {
|
||||
sub edit : Chained('jobsetChain') PathPart Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
requireProjectOwner($c, $c->stash->{project});
|
||||
|
@ -125,10 +233,9 @@ sub edit : Chained('jobset') PathPart Args(0) {
|
|||
}
|
||||
|
||||
|
||||
sub submit : Chained('jobset') PathPart Args(0) {
|
||||
sub submit : Chained('jobsetChain') PathPart Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
requireProjectOwner($c, $c->stash->{project});
|
||||
requirePost($c);
|
||||
|
||||
if (($c->request->params->{submit} // "") eq "delete") {
|
||||
|
@ -137,15 +244,17 @@ sub submit : Chained('jobset') PathPart Args(0) {
|
|||
$c->stash->{jobset}->builds->delete_all;
|
||||
$c->stash->{jobset}->delete;
|
||||
});
|
||||
return $c->res->redirect($c->uri_for($c->controller('Project')->action_for("view"), [$c->stash->{project}->name]));
|
||||
return $c->res->redirect($c->uri_for($c->controller('Project')->action_for("project"), [$c->stash->{project}->name]));
|
||||
}
|
||||
|
||||
txn_do($c->model('DB')->schema, sub {
|
||||
updateJobset($c, $c->stash->{jobset});
|
||||
});
|
||||
|
||||
$c->res->redirect($c->uri_for($self->action_for("index"),
|
||||
[$c->stash->{project}->name, $c->stash->{jobset}->name]) . "#tabs-configuration");
|
||||
my $newName = trim $c->stash->{params}->{name};
|
||||
my $oldName = trim $c->stash->{jobset}->name;
|
||||
unless ($oldName eq $newName) {
|
||||
$c->stash->{params}->{oldName} = $oldName;
|
||||
$c->stash->{jobsetName} = $newName;
|
||||
undef $c->stash->{jobset};
|
||||
}
|
||||
jobset_PUT($self, $c);
|
||||
}
|
||||
|
||||
|
||||
|
@ -153,32 +262,16 @@ sub nixExprPathFromParams {
|
|||
my ($c) = @_;
|
||||
|
||||
# The Nix expression path must be relative and can't contain ".." elements.
|
||||
my $nixExprPath = trim $c->request->params->{"nixexprpath"};
|
||||
my $nixExprPath = trim $c->stash->{params}->{"nixexprpath"};
|
||||
error($c, "Invalid Nix expression path: $nixExprPath") if $nixExprPath !~ /^$relPathRE$/;
|
||||
|
||||
my $nixExprInput = trim $c->request->params->{"nixexprinput"};
|
||||
my $nixExprInput = trim $c->stash->{params}->{"nixexprinput"};
|
||||
error($c, "Invalid Nix expression input name: $nixExprInput") unless $nixExprInput =~ /^\w+$/;
|
||||
|
||||
return ($nixExprPath, $nixExprInput);
|
||||
}
|
||||
|
||||
|
||||
sub checkInput {
|
||||
my ($c, $baseName) = @_;
|
||||
|
||||
my $inputName = trim $c->request->params->{"input-$baseName-name"};
|
||||
error($c, "Invalid input name: $inputName") unless $inputName =~ /^[[:alpha:]]\w*$/;
|
||||
|
||||
my $inputType = trim $c->request->params->{"input-$baseName-type"};
|
||||
error($c, "Invalid input type: $inputType") unless
|
||||
$inputType eq "svn" || $inputType eq "svn-checkout" || $inputType eq "hg" || $inputType eq "tarball" ||
|
||||
$inputType eq "string" || $inputType eq "path" || $inputType eq "boolean" || $inputType eq "bzr" || $inputType eq "bzr-checkout" ||
|
||||
$inputType eq "git" || $inputType eq "build" || $inputType eq "sysbuild" ;
|
||||
|
||||
return ($inputName, $inputType);
|
||||
}
|
||||
|
||||
|
||||
sub checkInputValue {
|
||||
my ($c, $type, $value) = @_;
|
||||
$value = trim $value;
|
||||
|
@ -191,50 +284,62 @@ sub checkInputValue {
|
|||
sub updateJobset {
|
||||
my ($c, $jobset) = @_;
|
||||
|
||||
my $jobsetName = trim $c->request->params->{"name"};
|
||||
my $jobsetName = $c->stash->{jobsetName} or $jobset->name;
|
||||
error($c, "Invalid jobset name: ‘$jobsetName’") if $jobsetName !~ /^$jobsetNameRE$/;
|
||||
|
||||
# When the expression is in a .scm file, assume it's a Guile + Guix
|
||||
# build expression.
|
||||
my $exprType =
|
||||
$c->request->params->{"nixexprpath"} =~ /.scm$/ ? "guile" : "nix";
|
||||
$c->stash->{params}->{"nixexprpath"} =~ /.scm$/ ? "guile" : "nix";
|
||||
|
||||
my ($nixExprPath, $nixExprInput) = nixExprPathFromParams $c;
|
||||
|
||||
$jobset->update(
|
||||
{ name => $jobsetName
|
||||
, description => trim($c->request->params->{"description"})
|
||||
, description => trim($c->stash->{params}->{"description"})
|
||||
, nixexprpath => $nixExprPath
|
||||
, nixexprinput => $nixExprInput
|
||||
, enabled => defined $c->request->params->{enabled} ? 1 : 0
|
||||
, enableemail => defined $c->request->params->{enableemail} ? 1 : 0
|
||||
, emailoverride => trim($c->request->params->{emailoverride}) || ""
|
||||
, hidden => defined $c->request->params->{visible} ? 0 : 1
|
||||
, keepnr => int(trim($c->request->params->{keepnr})) || 3
|
||||
, checkinterval => int(trim($c->request->params->{checkinterval}))
|
||||
, enabled => defined $c->stash->{params}->{enabled} ? 1 : 0
|
||||
, enableemail => defined $c->stash->{params}->{enableemail} ? 1 : 0
|
||||
, emailoverride => trim($c->stash->{params}->{emailoverride}) || ""
|
||||
, hidden => defined $c->stash->{params}->{visible} ? 0 : 1
|
||||
, keepnr => int(trim($c->stash->{params}->{keepnr})) || 3
|
||||
, checkinterval => int(trim($c->stash->{params}->{checkinterval}))
|
||||
, triggertime => $jobset->triggertime // time()
|
||||
});
|
||||
|
||||
my %inputNames;
|
||||
|
||||
# Process the inputs of this jobset.
|
||||
foreach my $param (keys %{$c->request->params}) {
|
||||
next unless $param =~ /^input-(\w+)-name$/;
|
||||
my $baseName = $1;
|
||||
next if $baseName eq "template";
|
||||
unless (defined $c->stash->{params}->{inputs}) {
|
||||
$c->stash->{params}->{inputs} = {};
|
||||
foreach my $param (keys %{$c->stash->{params}}) {
|
||||
next unless $param =~ /^input-(\w+)-name$/;
|
||||
my $baseName = $1;
|
||||
next if $baseName eq "template";
|
||||
$c->stash->{params}->{inputs}->{$c->stash->{params}->{$param}} = { type => $c->stash->{params}->{"input-$baseName-type"}, values => $c->stash->{params}->{"input-$baseName-values"} };
|
||||
unless ($baseName =~ /^\d+$/) { # non-numeric base name is an existing entry
|
||||
$c->stash->{params}->{inputs}->{$c->stash->{params}->{$param}}->{oldName} = $baseName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my ($inputName, $inputType) = checkInput($c, $baseName);
|
||||
foreach my $inputName (keys %{$c->stash->{params}->{inputs}}) {
|
||||
my $inputData = $c->stash->{params}->{inputs}->{$inputName};
|
||||
error($c, "Invalid input name: $inputName") unless $inputName =~ /^[[:alpha:]]\w*$/;
|
||||
|
||||
$inputNames{$inputName} = 1;
|
||||
my $inputType = $inputData->{type};
|
||||
error($c, "Invalid input type: $inputType") unless
|
||||
$inputType eq "svn" || $inputType eq "svn-checkout" || $inputType eq "hg" || $inputType eq "tarball" ||
|
||||
$inputType eq "string" || $inputType eq "path" || $inputType eq "boolean" || $inputType eq "bzr" || $inputType eq "bzr-checkout" ||
|
||||
$inputType eq "git" || $inputType eq "build" || $inputType eq "sysbuild" ;
|
||||
|
||||
my $input;
|
||||
if ($baseName =~ /^\d+$/) { # numeric base name is auto-generated, i.e. a new entry
|
||||
$input = $jobset->jobsetinputs->create(
|
||||
unless (defined $inputData->{oldName}) {
|
||||
$input = $jobset->jobsetinputs->update_or_create(
|
||||
{ name => $inputName
|
||||
, type => $inputType
|
||||
});
|
||||
} else { # it's an existing input
|
||||
$input = ($jobset->jobsetinputs->search({name => $baseName}))[0];
|
||||
$input = ($jobset->jobsetinputs->search({name => $inputData->{oldName}}))[0];
|
||||
die unless defined $input;
|
||||
$input->update({name => $inputName, type => $inputType});
|
||||
}
|
||||
|
@ -242,7 +347,7 @@ sub updateJobset {
|
|||
# Update the values for this input. Just delete all the
|
||||
# current ones, then create the new values.
|
||||
$input->jobsetinputalts->delete_all;
|
||||
my $values = $c->request->params->{"input-$baseName-values"};
|
||||
my $values = $inputData->{values};
|
||||
$values = [] unless defined $values;
|
||||
$values = [$values] unless ref($values) eq 'ARRAY';
|
||||
my $altnr = 0;
|
||||
|
@ -255,12 +360,12 @@ sub updateJobset {
|
|||
# Get rid of deleted inputs.
|
||||
my @inputs = $jobset->jobsetinputs->all;
|
||||
foreach my $input (@inputs) {
|
||||
$input->delete unless defined $inputNames{$input->name};
|
||||
$input->delete unless defined $c->stash->{params}->{inputs}->{$input->name};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub clone : Chained('jobset') PathPart('clone') Args(0) {
|
||||
sub clone : Chained('jobsetChain') PathPart('clone') Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
my $jobset = $c->stash->{jobset};
|
||||
|
@ -270,14 +375,14 @@ sub clone : Chained('jobset') PathPart('clone') Args(0) {
|
|||
}
|
||||
|
||||
|
||||
sub clone_submit : Chained('jobset') PathPart('clone/submit') Args(0) {
|
||||
sub clone_submit : Chained('jobsetChain') PathPart('clone/submit') Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
my $jobset = $c->stash->{jobset};
|
||||
requireProjectOwner($c, $jobset->project);
|
||||
requirePost($c);
|
||||
|
||||
my $newJobsetName = trim $c->request->params->{"newjobset"};
|
||||
my $newJobsetName = trim $c->stash->{params}->{"newjobset"};
|
||||
error($c, "Invalid jobset name: $newJobsetName") unless $newJobsetName =~ /^[[:alpha:]][\w\-]*$/;
|
||||
|
||||
my $newJobset;
|
||||
|
@ -304,7 +409,9 @@ sub clone_submit : Chained('jobset') PathPart('clone/submit') Args(0) {
|
|||
}
|
||||
|
||||
|
||||
sub evals : Chained('jobset') PathPart('evals') Args(0) {
|
||||
sub evals :Chained('jobsetChain') :PathPart('evals') :Args(0) :ActionClass('REST') { }
|
||||
|
||||
sub evals_GET {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
$c->stash->{template} = 'evals.tt';
|
||||
|
@ -318,12 +425,45 @@ sub evals : Chained('jobset') PathPart('evals') Args(0) {
|
|||
$c->stash->{page} = $page;
|
||||
$c->stash->{resultsPerPage} = $resultsPerPage;
|
||||
$c->stash->{total} = $evals->search({hasnewbuilds => 1})->count;
|
||||
$c->stash->{evals} = getEvals($self, $c, $evals, ($page - 1) * $resultsPerPage, $resultsPerPage)
|
||||
my $offset = ($page - 1) * $resultsPerPage;
|
||||
$c->stash->{evals} = getEvals($self, $c, $evals, $offset, $resultsPerPage);
|
||||
my %entity = (
|
||||
evals => [ $evals->search({ 'me.hasnewbuilds' => 1 }, {
|
||||
columns => [
|
||||
'me.hasnewbuilds',
|
||||
'me.id',
|
||||
'jobsetevalinputs.name',
|
||||
'jobsetevalinputs.altnr',
|
||||
'jobsetevalinputs.revision',
|
||||
'jobsetevalinputs.type',
|
||||
'jobsetevalinputs.uri',
|
||||
'jobsetevalinputs.dependency',
|
||||
'jobsetevalmembers.build',
|
||||
],
|
||||
join => [ 'jobsetevalinputs', 'jobsetevalmembers' ],
|
||||
collapse => 1,
|
||||
rows => $resultsPerPage,
|
||||
offset => $offset,
|
||||
order_by => "me.id DESC",
|
||||
}) ],
|
||||
first => "?page=1",
|
||||
last => "?page=" . POSIX::ceil($c->stash->{total}/$resultsPerPage)
|
||||
);
|
||||
if ($page > 1) {
|
||||
$entity{previous} = "?page=" . ($page - 1);
|
||||
}
|
||||
if ($page < POSIX::ceil($c->stash->{total}/$resultsPerPage)) {
|
||||
$entity{next} = "?page=" . ($page + 1);
|
||||
}
|
||||
$self->status_ok(
|
||||
$c,
|
||||
entity => \%entity
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
# Redirect to the latest finished evaluation of this jobset.
|
||||
sub latest_eval : Chained('jobset') PathPart('latest-eval') {
|
||||
sub latest_eval : Chained('jobsetChain') PathPart('latest-eval') {
|
||||
my ($self, $c, @args) = @_;
|
||||
my $eval = getLatestFinishedEval($c, $c->stash->{jobset})
|
||||
or notFound($c, "No evaluation found.");
|
||||
|
|
|
@ -7,17 +7,43 @@ use Hydra::Helper::Nix;
|
|||
use Hydra::Helper::CatalystUtils;
|
||||
|
||||
|
||||
sub project : Chained('/') PathPart('project') CaptureArgs(1) {
|
||||
sub projectChain :Chained('/') :PathPart('project') :CaptureArgs(1) {
|
||||
my ($self, $c, $projectName) = @_;
|
||||
|
||||
my $project = $c->model('DB::Projects')->find($projectName)
|
||||
or notFound($c, "Project $projectName doesn't exist.");
|
||||
my $project = $c->model('DB::Projects')->find($projectName, { columns => [
|
||||
"me.name",
|
||||
"me.displayName",
|
||||
"me.description",
|
||||
"me.enabled",
|
||||
"me.hidden",
|
||||
"me.homepage",
|
||||
"owner.username",
|
||||
"owner.fullname",
|
||||
"views.name",
|
||||
"releases.name",
|
||||
"releases.timestamp",
|
||||
"jobsets.name",
|
||||
], join => [ 'owner', 'views', 'releases', 'jobsets' ], order_by => { -desc => "releases.timestamp" }, collapse => 1 });
|
||||
|
||||
$c->stash->{project} = $project;
|
||||
if ($project) {
|
||||
$c->stash->{project} = $project;
|
||||
} else {
|
||||
if ($c->action->name eq "project" and $c->request->method eq "PUT") {
|
||||
$c->stash->{projectName} = $projectName;
|
||||
} else {
|
||||
$self->status_not_found(
|
||||
$c,
|
||||
message => "Project $projectName doesn't exist."
|
||||
);
|
||||
$c->detach;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub view : Chained('project') PathPart('') Args(0) {
|
||||
sub project :Chained('projectChain') :PathPart('') :Args(0) :ActionClass('REST::ForBrowsers') { }
|
||||
|
||||
sub project_GET {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
$c->stash->{template} = 'project.tt';
|
||||
|
@ -26,10 +52,83 @@ sub view : Chained('project') PathPart('') Args(0) {
|
|||
$c->stash->{jobsets} = [jobsetOverview($c, $c->stash->{project})];
|
||||
$c->stash->{releases} = [$c->stash->{project}->releases->search({},
|
||||
{order_by => ["timestamp DESC"]})];
|
||||
|
||||
$self->status_ok(
|
||||
$c,
|
||||
entity => $c->stash->{project}
|
||||
);
|
||||
}
|
||||
|
||||
sub project_PUT {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
if (defined $c->stash->{project}) {
|
||||
error($c, "Cannot rename project `$c->stash->{params}->{oldName}' over existing project `$c->stash->{project}->name") if defined $c->stash->{params}->{oldName};
|
||||
requireProjectOwner($c, $c->stash->{project});
|
||||
txn_do($c->model('DB')->schema, sub {
|
||||
updateProject($c, $c->stash->{project});
|
||||
});
|
||||
|
||||
if ($c->req->looks_like_browser) {
|
||||
$c->res->redirect($c->uri_for($self->action_for("project"), [$c->stash->{project}->name]) . "#tabs-configuration");
|
||||
} else {
|
||||
$self->status_no_content($c);
|
||||
}
|
||||
} elsif (defined $c->stash->{params}->{oldName}) {
|
||||
my $project = $c->model('DB::Projects')->find($c->stash->{params}->{oldName});
|
||||
if (defined $project) {
|
||||
requireProjectOwner($c, $project);
|
||||
txn_do($c->model('DB')->schema, sub {
|
||||
updateProject($c, $project);
|
||||
});
|
||||
|
||||
my $uri = $c->uri_for($self->action_for("project"), [$project->name]);
|
||||
|
||||
if ($c->req->looks_like_browser) {
|
||||
$c->res->redirect($uri . "#tabs-configuration");
|
||||
} else {
|
||||
$self->status_created(
|
||||
$c,
|
||||
location => "$uri",
|
||||
entity => { name => $project->name, uri => "$uri", type => "project" }
|
||||
);
|
||||
}
|
||||
} else {
|
||||
$self->status_not_found(
|
||||
$c,
|
||||
message => "Project $c->stash->{params}->{oldName} doesn't exist."
|
||||
);
|
||||
}
|
||||
} else {
|
||||
requireMayCreateProjects($c);
|
||||
error($c, "Invalid project name: ‘$c->stash->{projectName}’") if $c->stash->{projectName} !~ /^$projectNameRE$/;
|
||||
|
||||
my $project;
|
||||
txn_do($c->model('DB')->schema, sub {
|
||||
# Note: $projectName is validated in updateProject,
|
||||
# which will abort the transaction if the name isn't
|
||||
# valid. Idem for the owner.
|
||||
my $owner = $c->user->username;
|
||||
$project = $c->model('DB::Projects')->create(
|
||||
{name => $c->stash->{projectName}, displayname => "", owner => $owner});
|
||||
updateProject($c, $project);
|
||||
});
|
||||
|
||||
my $uri = $c->uri_for($self->action_for("project"), [$project->name]);
|
||||
if ($c->req->looks_like_browser) {
|
||||
$c->res->redirect($uri . "#tabs-configuration");
|
||||
} else {
|
||||
$self->status_created(
|
||||
$c,
|
||||
location => "$uri",
|
||||
entity => { name => $project->name, uri => "$uri", type => "project" }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub edit : Chained('project') PathPart Args(0) {
|
||||
sub edit : Chained('projectChain') PathPart Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
requireProjectOwner($c, $c->stash->{project});
|
||||
|
@ -39,12 +138,10 @@ sub edit : Chained('project') PathPart Args(0) {
|
|||
}
|
||||
|
||||
|
||||
sub submit : Chained('project') PathPart Args(0) {
|
||||
sub submit : Chained('projectChain') PathPart Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
requireProjectOwner($c, $c->stash->{project});
|
||||
requirePost($c);
|
||||
|
||||
if (($c->request->params->{submit} // "") eq "delete") {
|
||||
txn_do($c->model('DB')->schema, sub {
|
||||
$c->stash->{project}->jobsetevals->delete_all;
|
||||
|
@ -54,11 +151,14 @@ sub submit : Chained('project') PathPart Args(0) {
|
|||
return $c->res->redirect($c->uri_for("/"));
|
||||
}
|
||||
|
||||
txn_do($c->model('DB')->schema, sub {
|
||||
updateProject($c, $c->stash->{project});
|
||||
});
|
||||
|
||||
$c->res->redirect($c->uri_for($self->action_for("view"), [$c->stash->{project}->name]) . "#tabs-configuration");
|
||||
my $newName = trim $c->stash->{params}->{name};
|
||||
my $oldName = trim $c->stash->{project}->name;
|
||||
unless ($oldName eq $newName) {
|
||||
$c->stash->{params}->{oldName} = $oldName;
|
||||
$c->stash->{projectName} = $newName;
|
||||
undef $c->stash->{project};
|
||||
}
|
||||
project_PUT($self, $c);
|
||||
}
|
||||
|
||||
|
||||
|
@ -86,28 +186,13 @@ sub create : Path('/create-project') {
|
|||
sub create_submit : Path('/create-project/submit') {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
requireMayCreateProjects($c);
|
||||
$c->stash->{projectName} = trim $c->stash->{params}->{name};
|
||||
|
||||
my $projectName = trim $c->request->params->{name};
|
||||
|
||||
error($c, "Invalid project name: ‘$projectName’") if $projectName !~ /^$projectNameRE$/;
|
||||
|
||||
txn_do($c->model('DB')->schema, sub {
|
||||
# Note: $projectName is validated in updateProject,
|
||||
# which will abort the transaction if the name isn't
|
||||
# valid. Idem for the owner.
|
||||
my $owner = $c->check_user_roles('admin')
|
||||
? trim $c->request->params->{owner} : $c->user->username;
|
||||
my $project = $c->model('DB::Projects')->create(
|
||||
{name => $projectName, displayname => "", owner => $owner});
|
||||
updateProject($c, $project);
|
||||
});
|
||||
|
||||
$c->res->redirect($c->uri_for($self->action_for("view"), [$projectName]));
|
||||
project_PUT($self, $c);
|
||||
}
|
||||
|
||||
|
||||
sub create_jobset : Chained('project') PathPart('create-jobset') Args(0) {
|
||||
sub create_jobset : Chained('projectChain') PathPart('create-jobset') Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
requireProjectOwner($c, $c->stash->{project});
|
||||
|
@ -118,27 +203,12 @@ sub create_jobset : Chained('project') PathPart('create-jobset') Args(0) {
|
|||
}
|
||||
|
||||
|
||||
sub create_jobset_submit : Chained('project') PathPart('create-jobset/submit') Args(0) {
|
||||
sub create_jobset_submit : Chained('projectChain') PathPart('create-jobset/submit') Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
requireProjectOwner($c, $c->stash->{project});
|
||||
$c->stash->{jobsetName} = trim $c->stash->{params}->{name};
|
||||
|
||||
my $jobsetName = trim $c->request->params->{name};
|
||||
my $exprType =
|
||||
$c->request->params->{"nixexprpath"} =~ /.scm$/ ? "guile" : "nix";
|
||||
|
||||
error($c, "Invalid jobset name: ‘$jobsetName’") if $jobsetName !~ /^$jobsetNameRE$/;
|
||||
|
||||
txn_do($c->model('DB')->schema, sub {
|
||||
# Note: $jobsetName is validated in updateProject, which will
|
||||
# abort the transaction if the name isn't valid.
|
||||
my $jobset = $c->stash->{project}->jobsets->create(
|
||||
{name => $jobsetName, nixexprinput => "", nixexprpath => "", emailoverride => ""});
|
||||
Hydra::Controller::Jobset::updateJobset($c, $jobset);
|
||||
});
|
||||
|
||||
$c->res->redirect($c->uri_for($c->controller('Jobset')->action_for("index"),
|
||||
[$c->stash->{project}->name, $jobsetName]));
|
||||
Hydra::Controller::Jobset::jobset_PUT($self, $c);
|
||||
}
|
||||
|
||||
|
||||
|
@ -146,32 +216,32 @@ sub updateProject {
|
|||
my ($c, $project) = @_;
|
||||
|
||||
my $owner = $project->owner;
|
||||
if ($c->check_user_roles('admin')) {
|
||||
$owner = trim $c->request->params->{owner};
|
||||
if ($c->check_user_roles('admin') and defined $c->stash->{params}->{owner}) {
|
||||
$owner = trim $c->stash->{params}->{owner};
|
||||
error($c, "Invalid owner: $owner")
|
||||
unless defined $c->model('DB::Users')->find({username => $owner});
|
||||
}
|
||||
|
||||
my $projectName = trim $c->request->params->{name};
|
||||
my $projectName = $c->stash->{projectName} or $project->name;
|
||||
error($c, "Invalid project name: ‘$projectName’") if $projectName !~ /^$projectNameRE$/;
|
||||
|
||||
my $displayName = trim $c->request->params->{displayname};
|
||||
my $displayName = trim $c->stash->{params}->{displayname};
|
||||
error($c, "Invalid display name: $displayName") if $displayName eq "";
|
||||
|
||||
$project->update(
|
||||
{ name => $projectName
|
||||
, displayname => $displayName
|
||||
, description => trim($c->request->params->{description})
|
||||
, homepage => trim($c->request->params->{homepage})
|
||||
, enabled => defined $c->request->params->{enabled} ? 1 : 0
|
||||
, hidden => defined $c->request->params->{visible} ? 0 : 1
|
||||
, description => trim($c->stash->{params}->{description})
|
||||
, homepage => trim($c->stash->{params}->{homepage})
|
||||
, enabled => defined $c->stash->{params}->{enabled} ? 1 : 0
|
||||
, hidden => defined $c->stash->{params}->{visible} ? 0 : 1
|
||||
, owner => $owner
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
# Hydra::Base::Controller::ListBuilds needs this.
|
||||
sub get_builds : Chained('project') PathPart('') CaptureArgs(0) {
|
||||
sub get_builds : Chained('projectChain') PathPart('') CaptureArgs(0) {
|
||||
my ($self, $c) = @_;
|
||||
$c->stash->{allBuilds} = $c->stash->{project}->builds;
|
||||
$c->stash->{jobStatus} = $c->model('DB')->resultset('JobStatusForProject')
|
||||
|
@ -184,7 +254,7 @@ sub get_builds : Chained('project') PathPart('') CaptureArgs(0) {
|
|||
}
|
||||
|
||||
|
||||
sub create_view_submit : Chained('project') PathPart('create-view/submit') Args(0) {
|
||||
sub create_view_submit : Chained('projectChain') PathPart('create-view/submit') Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
requireProjectOwner($c, $c->stash->{project});
|
||||
|
@ -204,7 +274,7 @@ sub create_view_submit : Chained('project') PathPart('create-view/submit') Args(
|
|||
}
|
||||
|
||||
|
||||
sub create_view : Chained('project') PathPart('create-view') Args(0) {
|
||||
sub create_view : Chained('projectChain') PathPart('create-view') Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
requireProjectOwner($c, $c->stash->{project});
|
||||
|
@ -214,7 +284,7 @@ sub create_view : Chained('project') PathPart('create-view') Args(0) {
|
|||
}
|
||||
|
||||
|
||||
sub create_release : Chained('project') PathPart('create-release') Args(0) {
|
||||
sub create_release : Chained('projectChain') PathPart('create-release') Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
requireProjectOwner($c, $c->stash->{project});
|
||||
$c->stash->{template} = 'edit-release.tt';
|
||||
|
@ -222,7 +292,7 @@ sub create_release : Chained('project') PathPart('create-release') Args(0) {
|
|||
}
|
||||
|
||||
|
||||
sub create_release_submit : Chained('project') PathPart('create-release/submit') Args(0) {
|
||||
sub create_release_submit : Chained('projectChain') PathPart('create-release/submit') Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
requireProjectOwner($c, $c->stash->{project});
|
||||
|
|
|
@ -66,13 +66,13 @@ sub submit : Chained('release') PathPart('submit') Args(0) {
|
|||
txn_do($c->model('DB')->schema, sub {
|
||||
$c->stash->{release}->delete;
|
||||
});
|
||||
$c->res->redirect($c->uri_for($c->controller('Project')->action_for('view'),
|
||||
$c->res->redirect($c->uri_for($c->controller('Project')->action_for('project'),
|
||||
[$c->stash->{project}->name]));
|
||||
} else {
|
||||
txn_do($c->model('DB')->schema, sub {
|
||||
updateRelease($c, $c->stash->{release});
|
||||
});
|
||||
$c->res->redirect($c->uri_for($self->action_for("view"),
|
||||
$c->res->redirect($c->uri_for($self->action_for("project"),
|
||||
[$c->stash->{project}->name, $c->stash->{release}->name]));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,23 +37,44 @@ sub begin :Private {
|
|||
'sysbuild' => 'Build output (same system)'
|
||||
};
|
||||
$_->supportedInputTypes($c->stash->{inputTypes}) foreach @{$c->hydra_plugins};
|
||||
|
||||
$c->forward('deserialize');
|
||||
|
||||
$c->stash->{params} = $c->request->data or $c->request->params;
|
||||
unless (defined $c->stash->{params} and %{$c->stash->{params}}) {
|
||||
$c->stash->{params} = $c->request->params;
|
||||
}
|
||||
}
|
||||
|
||||
sub deserialize :ActionClass('Deserialize') { }
|
||||
|
||||
|
||||
sub index :Path :Args(0) {
|
||||
my ($self, $c) = @_;
|
||||
$c->stash->{template} = 'overview.tt';
|
||||
$c->stash->{projects} = [$c->model('DB::Projects')->search(isAdmin($c) ? {} : {hidden => 0}, {order_by => 'name'})];
|
||||
$c->stash->{newsItems} = [$c->model('DB::NewsItems')->search({}, { order_by => ['createtime DESC'], rows => 5 })];
|
||||
$self->status_ok(
|
||||
$c,
|
||||
entity => [$c->model('DB::Projects')->search(isAdmin($c) ? {} : {hidden => 0}, {
|
||||
order_by => 'name',
|
||||
columns => [ 'name', 'displayname' ]
|
||||
})]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
sub queue :Local {
|
||||
sub queue :Local :Args(0) :ActionClass('REST') { }
|
||||
|
||||
sub queue_GET {
|
||||
my ($self, $c) = @_;
|
||||
$c->stash->{template} = 'queue.tt';
|
||||
$c->stash->{queue} = [$c->model('DB::Builds')->search(
|
||||
{finished => 0}, { join => ['project'], order_by => ["priority DESC", "id"], columns => [@buildListColumns], '+select' => ['project.enabled'], '+as' => ['enabled'] })];
|
||||
$c->stash->{flashMsg} //= $c->flash->{buildMsg};
|
||||
$self->status_ok(
|
||||
$c,
|
||||
entity => [$c->model('DB::Builds')->search(
|
||||
{finished => 0}, { join => ['project'], order_by => ["priority DESC", "id"], columns => [@buildListColumns], '+select' => ['project.enabled'], '+as' => ['enabled'] })]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
@ -71,13 +92,32 @@ sub timeline :Local {
|
|||
}
|
||||
|
||||
|
||||
sub status :Local {
|
||||
sub status :Local :Args(0) :ActionClass('REST') { }
|
||||
|
||||
sub status_GET {
|
||||
my ($self, $c) = @_;
|
||||
$c->stash->{steps} = [ $c->model('DB::BuildSteps')->search(
|
||||
{ 'me.busy' => 1, 'build.finished' => 0, 'build.busy' => 1 },
|
||||
{ join => [ 'build' ]
|
||||
, order_by => [ 'machine' ]
|
||||
} ) ];
|
||||
$self->status_ok(
|
||||
$c,
|
||||
entity => [ $c->model('DB::BuildSteps')->search(
|
||||
{ 'me.busy' => 1, 'build.finished' => 0, 'build.busy' => 1 },
|
||||
{ join => { build => [ 'project', 'job', 'jobset' ] },
|
||||
columns => [
|
||||
'me.machine',
|
||||
'me.system',
|
||||
'me.stepnr',
|
||||
'me.drvpath',
|
||||
'me.starttime',
|
||||
'build.id',
|
||||
{
|
||||
'build.project.name' => 'project.name',
|
||||
'build.jobset.name' => 'jobset.name',
|
||||
'build.job.name' => 'job.name'
|
||||
}
|
||||
],
|
||||
order_by => [ 'machine' ]
|
||||
}
|
||||
) ]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
@ -181,7 +221,8 @@ sub end : ActionClass('RenderView') {
|
|||
$c->forward('View::JSON');
|
||||
}
|
||||
|
||||
elsif (scalar @{$c->error}) {
|
||||
if (scalar @{$c->error}) {
|
||||
$c->stash->{resource} = { errors => "$c->error" };
|
||||
$c->stash->{template} = 'error.tt';
|
||||
$c->stash->{errors} = $c->error;
|
||||
$c->response->status(500) if $c->response->status == 200;
|
||||
|
@ -190,9 +231,19 @@ sub end : ActionClass('RenderView') {
|
|||
$c->response->status . " " . HTTP::Status::status_message($c->response->status);
|
||||
}
|
||||
$c->clear_errors;
|
||||
} elsif (defined $c->stash->{resource} and
|
||||
(ref $c->stash->{resource} eq ref {}) and
|
||||
defined $c->stash->{resource}->{error}) {
|
||||
$c->stash->{template} = 'error.tt';
|
||||
$c->stash->{httpStatus} =
|
||||
$c->response->status . " " . HTTP::Status::status_message($c->response->status);
|
||||
}
|
||||
|
||||
$c->forward('serialize');
|
||||
}
|
||||
|
||||
sub serialize : ActionClass('Serialize') { }
|
||||
|
||||
|
||||
sub nar :Local :Args(1) {
|
||||
my ($self, $c, $path) = @_;
|
||||
|
|
|
@ -3,7 +3,7 @@ package Hydra::Controller::User;
|
|||
use utf8;
|
||||
use strict;
|
||||
use warnings;
|
||||
use base 'Catalyst::Controller';
|
||||
use base 'Hydra::Base::Controller::REST';
|
||||
use Crypt::RandPasswd;
|
||||
use Digest::SHA1 qw(sha1_hex);
|
||||
use Hydra::Helper::Nix;
|
||||
|
@ -13,31 +13,60 @@ use Hydra::Helper::CatalystUtils;
|
|||
__PACKAGE__->config->{namespace} = '';
|
||||
|
||||
|
||||
sub login :Local {
|
||||
sub login :Local :Args(0) :ActionClass('REST::ForBrowsers') { }
|
||||
|
||||
sub login_GET {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
my $username = $c->request->params->{username} || "";
|
||||
my $password = $c->request->params->{password} || "";
|
||||
|
||||
if ($username eq "" && $password eq "" && !defined $c->session->{referer}) {
|
||||
my $baseurl = $c->uri_for('/');
|
||||
my $referer = $c->request->referer;
|
||||
$c->session->{referer} = $referer if defined $referer && $referer =~ m/^($baseurl)/;
|
||||
}
|
||||
|
||||
if ($username && $password) {
|
||||
backToReferer($c) if $c->authenticate({username => $username, password => $password});
|
||||
$c->stash->{errorMsg} = "Bad username or password.";
|
||||
}
|
||||
my $baseurl = $c->uri_for('/');
|
||||
my $referer = $c->request->referer;
|
||||
$c->session->{referer} = $referer if defined $referer && $referer =~ m/^($baseurl)/;
|
||||
|
||||
$c->stash->{template} = 'login.tt';
|
||||
}
|
||||
|
||||
sub login_POST {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
sub logout :Local {
|
||||
my $username;
|
||||
my $password;
|
||||
|
||||
$username = $c->stash->{params}->{username};
|
||||
$password = $c->stash->{params}->{password};
|
||||
|
||||
if ($username && $password) {
|
||||
if ($c->authenticate({username => $username, password => $password})) {
|
||||
if ($c->request->looks_like_browser) {
|
||||
backToReferer($c);
|
||||
} else {
|
||||
currentUser_GET($self, $c);
|
||||
}
|
||||
} else {
|
||||
$self->status_forbidden($c, message => "Bad username or password.");
|
||||
if ($c->request->looks_like_browser) {
|
||||
login_GET($self, $c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub logout :Local :Args(0) :ActionClass('REST::ForBrowsers') { }
|
||||
|
||||
sub logout_POST {
|
||||
my ($self, $c) = @_;
|
||||
$c->logout;
|
||||
$c->response->redirect($c->request->referer || $c->uri_for('/'));
|
||||
if ($c->request->looks_like_browser) {
|
||||
$c->response->redirect($c->request->referer || $c->uri_for('/'));
|
||||
} else {
|
||||
$self->status_no_content($c);
|
||||
}
|
||||
}
|
||||
|
||||
sub logout_GET {
|
||||
# Probably a better way to do this
|
||||
my ($self, $c) = @_;
|
||||
logout_POST($self, $c);
|
||||
}
|
||||
|
||||
|
||||
|
@ -116,6 +145,24 @@ sub register :Local Args(0) {
|
|||
}
|
||||
|
||||
|
||||
sub currentUser :Path('/current-user') :ActionClass('REST') { }
|
||||
|
||||
sub currentUser_GET {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
requireLogin($c) if !$c->user_exists;
|
||||
|
||||
$self->status_ok(
|
||||
$c,
|
||||
entity => $c->model('DB::Users')->find({ 'me.username' => $c->user->username}, {
|
||||
columns => [ "me.fullname", "me.emailaddress", "me.username", "userroles.role" ]
|
||||
, join => [ "userroles" ]
|
||||
, collapse => 1
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
sub user :Chained('/') PathPart('user') CaptureArgs(1) {
|
||||
my ($self, $c, $userName) = @_;
|
||||
|
||||
|
@ -139,7 +186,9 @@ sub deleteUser {
|
|||
}
|
||||
|
||||
|
||||
sub edit :Chained('user') Args(0) {
|
||||
sub edit :Chained('user') :Args(0) :ActionClass('REST::ForBrowsers') { }
|
||||
|
||||
sub edit_GET {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
my $user = $c->stash->{user};
|
||||
|
@ -148,18 +197,26 @@ sub edit :Chained('user') Args(0) {
|
|||
|
||||
$c->session->{referer} = $c->request->referer if !defined $c->session->{referer};
|
||||
|
||||
if ($c->request->method ne "POST") {
|
||||
$c->stash->{fullname} = $user->fullname;
|
||||
$c->stash->{emailonerror} = $user->emailonerror;
|
||||
return;
|
||||
}
|
||||
$c->stash->{fullname} = $user->fullname;
|
||||
|
||||
if (($c->request->params->{submit} // "") eq "delete") {
|
||||
$c->stash->{emailonerror} = $user->emailonerror;
|
||||
}
|
||||
|
||||
sub edit_POST {
|
||||
my ($self, $c) = @_;
|
||||
|
||||
my $user = $c->stash->{user};
|
||||
|
||||
$c->stash->{template} = 'user.tt';
|
||||
|
||||
$c->session->{referer} = $c->request->referer if !defined $c->session->{referer};
|
||||
|
||||
if (($c->stash->{params}->{submit} // "") eq "delete") {
|
||||
deleteUser($self, $c, $user);
|
||||
backToReferer($c);
|
||||
}
|
||||
|
||||
if (($c->request->params->{submit} // "") eq "reset-password") {
|
||||
if (($c->stash->{params}->{submit} // "") eq "reset-password") {
|
||||
$c->stash->{json} = {};
|
||||
error($c, "No email address is set for this user.")
|
||||
unless $user->emailaddress;
|
||||
|
@ -176,7 +233,7 @@ sub edit :Chained('user') Args(0) {
|
|||
return;
|
||||
}
|
||||
|
||||
my $fullName = trim $c->req->params->{fullname};
|
||||
my $fullName = trim $c->stash->{params}->{fullname};
|
||||
|
||||
txn_do($c->model('DB')->schema, sub {
|
||||
|
||||
|
@ -184,15 +241,15 @@ sub edit :Chained('user') Args(0) {
|
|||
|
||||
$user->update(
|
||||
{ fullname => $fullName
|
||||
, emailonerror => $c->request->params->{"emailonerror"} ? 1 : 0
|
||||
, emailonerror => $c->stash->{params}->{"emailonerror"} ? 1 : 0
|
||||
});
|
||||
|
||||
my $password = $c->req->params->{password} // "";
|
||||
my $password = $c->stash->{params}->{password} // "";
|
||||
if ($password ne "") {
|
||||
error($c, "You must specify a password of at least 6 characters.")
|
||||
unless isValidPassword($password);
|
||||
error($c, "The passwords you specified did not match.")
|
||||
if $password ne trim $c->req->params->{password2};
|
||||
if $password ne trim $c->stash->{params}->{password2};
|
||||
setPassword($user, $password);
|
||||
}
|
||||
|
||||
|
@ -204,7 +261,11 @@ sub edit :Chained('user') Args(0) {
|
|||
|
||||
});
|
||||
|
||||
backToReferer($c);
|
||||
if ($c->request->looks_like_browser) {
|
||||
backToReferer($c);
|
||||
} else {
|
||||
$self->status_no_content($c);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -118,7 +118,7 @@ sub submit : Chained('view') PathPart('submit') Args(0) {
|
|||
requireProjectOwner($c, $c->stash->{project});
|
||||
if (($c->request->params->{submit} || "") eq "delete") {
|
||||
$c->stash->{view}->delete;
|
||||
$c->res->redirect($c->uri_for($c->controller('Project')->action_for('view'),
|
||||
$c->res->redirect($c->uri_for($c->controller('Project')->action_for('project'),
|
||||
[$c->stash->{project}->name]));
|
||||
}
|
||||
txn_do($c->model('DB')->schema, sub {
|
||||
|
@ -224,7 +224,7 @@ sub result : Chained('view') PathPart('') {
|
|||
notFound($c, "View doesn't have a job named ‘$jobName’" . ($system ? " for ‘$system’" : "") . ".")
|
||||
unless defined $build;
|
||||
error($c, "Job `$jobName' isn't unique.") if @others;
|
||||
return $c->res->redirect($c->uri_for($c->controller('Build')->action_for('view_build'),
|
||||
return $c->res->redirect($c->uri_for($c->controller('Build')->action_for('build'),
|
||||
[$build->{build}->id], @args));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -202,7 +202,7 @@ sub sendEmail {
|
|||
# always returns a request parameter as a list.
|
||||
sub paramToList {
|
||||
my ($c, $name) = @_;
|
||||
my $x = $c->request->params->{$name};
|
||||
my $x = $c->stash->{params}->{$name};
|
||||
return () unless defined $x;
|
||||
return @$x if ref($x) eq 'ARRAY';
|
||||
return ($x);
|
||||
|
|
|
@ -15,6 +15,18 @@ use warnings;
|
|||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
=head1 COMPONENTS LOADED
|
||||
|
||||
=over 4
|
||||
|
||||
=item * L<Hydra::Component::ToJSON>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->load_components("+Hydra::Component::ToJSON");
|
||||
|
||||
=head1 TABLE: C<BuildInputs>
|
||||
|
||||
=cut
|
||||
|
@ -156,7 +168,7 @@ __PACKAGE__->belongs_to(
|
|||
);
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-01-22 13:29:36
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:byU/SLN03zNJlSFbi/3Bcg
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-06-13 01:54:50
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:tKZAybbNaRIMs9n5tHkqPw
|
||||
|
||||
1;
|
||||
|
|
|
@ -15,6 +15,18 @@ use warnings;
|
|||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
=head1 COMPONENTS LOADED
|
||||
|
||||
=over 4
|
||||
|
||||
=item * L<Hydra::Component::ToJSON>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->load_components("+Hydra::Component::ToJSON");
|
||||
|
||||
=head1 TABLE: C<BuildOutputs>
|
||||
|
||||
=cut
|
||||
|
@ -82,8 +94,8 @@ __PACKAGE__->belongs_to(
|
|||
);
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-01-30 16:22:11
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:UpVoKdd3OwMvlvyMjcYNVA
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-06-13 01:54:50
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:V8MbzKvZNEaeHBJV67+ZMQ
|
||||
|
||||
|
||||
# You can replace this text with custom code or comments, and it will be preserved on regeneration
|
||||
|
|
|
@ -15,6 +15,18 @@ use warnings;
|
|||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
=head1 COMPONENTS LOADED
|
||||
|
||||
=over 4
|
||||
|
||||
=item * L<Hydra::Component::ToJSON>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->load_components("+Hydra::Component::ToJSON");
|
||||
|
||||
=head1 TABLE: C<BuildProducts>
|
||||
|
||||
=cut
|
||||
|
@ -138,8 +150,8 @@ __PACKAGE__->belongs_to(
|
|||
);
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-01-22 13:29:36
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:KHwh/Np40jxKXc3ijMImEQ
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-06-13 01:54:50
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:+0LkZiaRL5tGJvbLxnwD/g
|
||||
|
||||
# You can replace this text with custom content, and it will be preserved on regeneration
|
||||
1;
|
||||
|
|
|
@ -15,6 +15,18 @@ use warnings;
|
|||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
=head1 COMPONENTS LOADED
|
||||
|
||||
=over 4
|
||||
|
||||
=item * L<Hydra::Component::ToJSON>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->load_components("+Hydra::Component::ToJSON");
|
||||
|
||||
=head1 TABLE: C<BuildStepOutputs>
|
||||
|
||||
=cut
|
||||
|
@ -107,8 +119,8 @@ __PACKAGE__->belongs_to(
|
|||
);
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-01-30 16:22:11
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:dC1yX7arRVu9K3wG9dAjCg
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-06-13 01:54:50
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:A/4v3ugXYbuYoKPlOvC6mg
|
||||
|
||||
|
||||
# You can replace this text with custom code or comments, and it will be preserved on regeneration
|
||||
|
|
|
@ -15,6 +15,18 @@ use warnings;
|
|||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
=head1 COMPONENTS LOADED
|
||||
|
||||
=over 4
|
||||
|
||||
=item * L<Hydra::Component::ToJSON>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->load_components("+Hydra::Component::ToJSON");
|
||||
|
||||
=head1 TABLE: C<BuildSteps>
|
||||
|
||||
=cut
|
||||
|
@ -154,7 +166,7 @@ __PACKAGE__->has_many(
|
|||
);
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-01-30 16:36:03
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ZiA1nv73Fpp0/DTi4sLfEQ
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-06-13 01:54:50
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:OZsXJniZ/7EB2iSz7p5y4A
|
||||
|
||||
1;
|
||||
|
|
|
@ -15,6 +15,18 @@ use warnings;
|
|||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
=head1 COMPONENTS LOADED
|
||||
|
||||
=over 4
|
||||
|
||||
=item * L<Hydra::Component::ToJSON>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->load_components("+Hydra::Component::ToJSON");
|
||||
|
||||
=head1 TABLE: C<Builds>
|
||||
|
||||
=cut
|
||||
|
@ -457,8 +469,8 @@ __PACKAGE__->has_many(
|
|||
);
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-05-03 14:35:11
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:aYVEk+AeDsgTRi5GAqOhEw
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-06-13 01:54:50
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:isCEXACY/PwkvgKHcXvAIg
|
||||
|
||||
__PACKAGE__->has_many(
|
||||
"dependents",
|
||||
|
|
|
@ -15,6 +15,18 @@ use warnings;
|
|||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
=head1 COMPONENTS LOADED
|
||||
|
||||
=over 4
|
||||
|
||||
=item * L<Hydra::Component::ToJSON>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->load_components("+Hydra::Component::ToJSON");
|
||||
|
||||
=head1 TABLE: C<CachedBazaarInputs>
|
||||
|
||||
=cut
|
||||
|
@ -71,8 +83,8 @@ __PACKAGE__->add_columns(
|
|||
__PACKAGE__->set_primary_key("uri", "revision");
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07014 @ 2011-12-05 14:15:43
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ONhBo6Xhq7uwYFdEzbp3dg
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-06-13 01:54:50
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:zvun8uhxwrr7B8EsqBoCjA
|
||||
|
||||
|
||||
# You can replace this text with custom content, and it will be preserved on regeneration
|
||||
|
|
|
@ -15,6 +15,18 @@ use warnings;
|
|||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
=head1 COMPONENTS LOADED
|
||||
|
||||
=over 4
|
||||
|
||||
=item * L<Hydra::Component::ToJSON>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->load_components("+Hydra::Component::ToJSON");
|
||||
|
||||
=head1 TABLE: C<CachedCVSInputs>
|
||||
|
||||
=cut
|
||||
|
@ -87,8 +99,8 @@ __PACKAGE__->add_columns(
|
|||
__PACKAGE__->set_primary_key("uri", "module", "sha256hash");
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07014 @ 2011-12-05 14:15:43
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:IcSVN/tlfQQtX88Ix+aKnw
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-06-13 01:54:50
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:Vi1qzjW52Lnsl0JSmGzy0w
|
||||
|
||||
# You can replace this text with custom content, and it will be preserved on regeneration
|
||||
1;
|
||||
|
|
|
@ -15,6 +15,18 @@ use warnings;
|
|||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
=head1 COMPONENTS LOADED
|
||||
|
||||
=over 4
|
||||
|
||||
=item * L<Hydra::Component::ToJSON>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->load_components("+Hydra::Component::ToJSON");
|
||||
|
||||
=head1 TABLE: C<CachedGitInputs>
|
||||
|
||||
=cut
|
||||
|
@ -80,7 +92,7 @@ __PACKAGE__->add_columns(
|
|||
__PACKAGE__->set_primary_key("uri", "branch", "revision");
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07014 @ 2011-12-05 14:15:43
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:fx3yosWMmJ+MnvL/dSWtFA
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-06-13 01:54:50
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:I4hI02FKRMkw76WV/KBocA
|
||||
|
||||
1;
|
||||
|
|
|
@ -15,6 +15,18 @@ use warnings;
|
|||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
=head1 COMPONENTS LOADED
|
||||
|
||||
=over 4
|
||||
|
||||
=item * L<Hydra::Component::ToJSON>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->load_components("+Hydra::Component::ToJSON");
|
||||
|
||||
=head1 TABLE: C<CachedHgInputs>
|
||||
|
||||
=cut
|
||||
|
@ -80,8 +92,8 @@ __PACKAGE__->add_columns(
|
|||
__PACKAGE__->set_primary_key("uri", "branch", "revision");
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07014 @ 2011-12-05 14:15:43
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:xFLnuCBAcJCg+N3b4aajZQ
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-06-13 01:54:50
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:qS/eiiZXmpc7KpTHdtaT7g
|
||||
|
||||
|
||||
# You can replace this text with custom content, and it will be preserved on regeneration
|
||||
|
|
|
@ -15,6 +15,18 @@ use warnings;
|
|||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
=head1 COMPONENTS LOADED
|
||||
|
||||
=over 4
|
||||
|
||||
=item * L<Hydra::Component::ToJSON>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->load_components("+Hydra::Component::ToJSON");
|
||||
|
||||
=head1 TABLE: C<CachedPathInputs>
|
||||
|
||||
=cut
|
||||
|
@ -78,7 +90,7 @@ __PACKAGE__->add_columns(
|
|||
__PACKAGE__->set_primary_key("srcpath", "sha256hash");
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07014 @ 2011-12-05 14:15:43
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:4KzXhMnUldVgNuuNXWIYjw
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-06-13 01:54:50
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:28rja0vR1glJJ15hzVfjsQ
|
||||
|
||||
1;
|
||||
|
|
|
@ -15,6 +15,18 @@ use warnings;
|
|||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
=head1 COMPONENTS LOADED
|
||||
|
||||
=over 4
|
||||
|
||||
=item * L<Hydra::Component::ToJSON>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->load_components("+Hydra::Component::ToJSON");
|
||||
|
||||
=head1 TABLE: C<CachedSubversionInputs>
|
||||
|
||||
=cut
|
||||
|
@ -71,7 +83,7 @@ __PACKAGE__->add_columns(
|
|||
__PACKAGE__->set_primary_key("uri", "revision");
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07014 @ 2011-12-05 14:15:43
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:1rjwWtZXGEowHqhfjLqjmA
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-06-13 01:54:50
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:3qXfnvkOVj25W94bfhQ65w
|
||||
|
||||
1;
|
||||
|
|
|
@ -15,6 +15,18 @@ use warnings;
|
|||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
=head1 COMPONENTS LOADED
|
||||
|
||||
=over 4
|
||||
|
||||
=item * L<Hydra::Component::ToJSON>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->load_components("+Hydra::Component::ToJSON");
|
||||
|
||||
=head1 TABLE: C<Jobs>
|
||||
|
||||
=cut
|
||||
|
@ -126,7 +138,7 @@ __PACKAGE__->belongs_to(
|
|||
);
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-05-23 16:09:46
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:JgxEaCz/TW9YKa+HavRzXw
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-06-13 01:54:50
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:t2CCfUjFEz/lO4szROz1AQ
|
||||
|
||||
1;
|
||||
|
|
|
@ -15,6 +15,18 @@ use warnings;
|
|||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
=head1 COMPONENTS LOADED
|
||||
|
||||
=over 4
|
||||
|
||||
=item * L<Hydra::Component::ToJSON>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->load_components("+Hydra::Component::ToJSON");
|
||||
|
||||
=head1 TABLE: C<JobsetEvalInputs>
|
||||
|
||||
=cut
|
||||
|
@ -154,8 +166,8 @@ __PACKAGE__->belongs_to(
|
|||
);
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-01-22 13:29:36
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ng+Q6tMX5EJMD7DxRWVy7Q
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-06-13 01:54:50
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:1Dp8B58leBLh4GK0GPw2zg
|
||||
|
||||
|
||||
# You can replace this text with custom code or comments, and it will be preserved on regeneration
|
||||
|
|
|
@ -15,6 +15,18 @@ use warnings;
|
|||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
=head1 COMPONENTS LOADED
|
||||
|
||||
=over 4
|
||||
|
||||
=item * L<Hydra::Component::ToJSON>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->load_components("+Hydra::Component::ToJSON");
|
||||
|
||||
=head1 TABLE: C<JobsetEvalMembers>
|
||||
|
||||
=cut
|
||||
|
@ -98,8 +110,8 @@ __PACKAGE__->belongs_to(
|
|||
);
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-01-22 13:29:36
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:EVwSR9WBqbBdIHq1ANQMHg
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-06-13 01:54:50
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ccPNQe/QnSjTAC3uGWe8Ng
|
||||
|
||||
|
||||
# You can replace this text with custom content, and it will be preserved on regeneration
|
||||
|
|
|
@ -15,6 +15,18 @@ use warnings;
|
|||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
=head1 COMPONENTS LOADED
|
||||
|
||||
=over 4
|
||||
|
||||
=item * L<Hydra::Component::ToJSON>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->load_components("+Hydra::Component::ToJSON");
|
||||
|
||||
=head1 TABLE: C<JobsetEvals>
|
||||
|
||||
=cut
|
||||
|
@ -176,8 +188,8 @@ __PACKAGE__->belongs_to(
|
|||
);
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-01-22 13:29:36
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:qElGj6zzuI0xo426np3r1w
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-06-13 01:54:50
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:SlEiF8oN6FBK262uSiMKiw
|
||||
|
||||
__PACKAGE__->has_many(
|
||||
"buildIds",
|
||||
|
|
|
@ -15,6 +15,18 @@ use warnings;
|
|||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
=head1 COMPONENTS LOADED
|
||||
|
||||
=over 4
|
||||
|
||||
=item * L<Hydra::Component::ToJSON>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->load_components("+Hydra::Component::ToJSON");
|
||||
|
||||
=head1 TABLE: C<JobsetInputAlts>
|
||||
|
||||
=cut
|
||||
|
@ -109,7 +121,7 @@ __PACKAGE__->belongs_to(
|
|||
);
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-01-22 13:29:36
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:M3pNBRLfxgSScrPj1zaajA
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-06-13 01:54:50
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:UUO37lIuEYm0GiR92m/fyA
|
||||
|
||||
1;
|
||||
|
|
|
@ -15,6 +15,18 @@ use warnings;
|
|||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
=head1 COMPONENTS LOADED
|
||||
|
||||
=over 4
|
||||
|
||||
=item * L<Hydra::Component::ToJSON>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->load_components("+Hydra::Component::ToJSON");
|
||||
|
||||
=head1 TABLE: C<JobsetInputs>
|
||||
|
||||
=cut
|
||||
|
@ -130,7 +142,7 @@ __PACKAGE__->has_many(
|
|||
);
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-01-22 13:29:36
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:xjioYUPo6visoLAVDkDZ0Q
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-06-13 01:54:50
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:UXBzqO0vHPql4LYyXpgEQg
|
||||
|
||||
1;
|
||||
|
|
|
@ -15,6 +15,18 @@ use warnings;
|
|||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
=head1 COMPONENTS LOADED
|
||||
|
||||
=over 4
|
||||
|
||||
=item * L<Hydra::Component::ToJSON>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->load_components("+Hydra::Component::ToJSON");
|
||||
|
||||
=head1 TABLE: C<Jobsets>
|
||||
|
||||
=cut
|
||||
|
@ -260,7 +272,7 @@ __PACKAGE__->belongs_to(
|
|||
);
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-05-02 14:50:55
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:q4amPCWRoWMThnRa/n/y1w
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-06-13 01:54:50
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:tsGR8MhZRIUeNwpcVczMUw
|
||||
|
||||
1;
|
||||
|
|
|
@ -15,6 +15,18 @@ use warnings;
|
|||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
=head1 COMPONENTS LOADED
|
||||
|
||||
=over 4
|
||||
|
||||
=item * L<Hydra::Component::ToJSON>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->load_components("+Hydra::Component::ToJSON");
|
||||
|
||||
=head1 TABLE: C<NewsItems>
|
||||
|
||||
=cut
|
||||
|
@ -88,7 +100,7 @@ __PACKAGE__->belongs_to(
|
|||
);
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-01-22 13:29:36
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:lnA5Utkwk5WTyKA/M5mlyg
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-06-13 01:54:50
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:3CRNsvd+YnZp9c80tuZREQ
|
||||
|
||||
1;
|
||||
|
|
|
@ -15,6 +15,18 @@ use warnings;
|
|||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
=head1 COMPONENTS LOADED
|
||||
|
||||
=over 4
|
||||
|
||||
=item * L<Hydra::Component::ToJSON>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->load_components("+Hydra::Component::ToJSON");
|
||||
|
||||
=head1 TABLE: C<ProjectMembers>
|
||||
|
||||
=cut
|
||||
|
@ -91,8 +103,8 @@ __PACKAGE__->belongs_to(
|
|||
);
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-01-22 13:29:36
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:zW87n6E7xWaShcFbgFkVuw
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-06-13 01:54:50
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:imPoiaitrTbX0vVNlF6dPA
|
||||
|
||||
|
||||
# You can replace this text with custom content, and it will be preserved on regeneration
|
||||
|
|
|
@ -15,6 +15,18 @@ use warnings;
|
|||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
=head1 COMPONENTS LOADED
|
||||
|
||||
=over 4
|
||||
|
||||
=item * L<Hydra::Component::ToJSON>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->load_components("+Hydra::Component::ToJSON");
|
||||
|
||||
=head1 TABLE: C<Projects>
|
||||
|
||||
=cut
|
||||
|
@ -255,8 +267,8 @@ Composing rels: L</projectmembers> -> username
|
|||
__PACKAGE__->many_to_many("usernames", "projectmembers", "username");
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-01-22 13:29:36
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:OCuhmxs8pZxvmk81eVLLcQ
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-06-13 01:54:50
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:RffghAo9jAaqYk41y1Sdqw
|
||||
# These lines were loaded from '/home/rbvermaa/src/hydra/src/lib/Hydra/Schema/Projects.pm' found in @INC.
|
||||
# They are now part of the custom portion of this file
|
||||
# for you to hand-edit. If you do not either delete
|
||||
|
|
|
@ -15,6 +15,18 @@ use warnings;
|
|||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
=head1 COMPONENTS LOADED
|
||||
|
||||
=over 4
|
||||
|
||||
=item * L<Hydra::Component::ToJSON>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->load_components("+Hydra::Component::ToJSON");
|
||||
|
||||
=head1 TABLE: C<ReleaseMembers>
|
||||
|
||||
=cut
|
||||
|
@ -123,7 +135,7 @@ __PACKAGE__->belongs_to(
|
|||
);
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-01-22 13:29:36
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:eP00w5UJp1uTtiB7D5IhTQ
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-06-13 01:54:50
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:7M7WPlGQT6rNHKJ+82/KSA
|
||||
|
||||
1;
|
||||
|
|
|
@ -15,6 +15,18 @@ use warnings;
|
|||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
=head1 COMPONENTS LOADED
|
||||
|
||||
=over 4
|
||||
|
||||
=item * L<Hydra::Component::ToJSON>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->load_components("+Hydra::Component::ToJSON");
|
||||
|
||||
=head1 TABLE: C<Releases>
|
||||
|
||||
=cut
|
||||
|
@ -107,7 +119,7 @@ __PACKAGE__->has_many(
|
|||
);
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-01-22 13:29:36
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:UTUE3Hb89fT7prwnwwBgvQ
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-06-13 01:54:50
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:qISBiwvboB8dIdinaE45mg
|
||||
|
||||
1;
|
||||
|
|
|
@ -15,6 +15,18 @@ use warnings;
|
|||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
=head1 COMPONENTS LOADED
|
||||
|
||||
=over 4
|
||||
|
||||
=item * L<Hydra::Component::ToJSON>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->load_components("+Hydra::Component::ToJSON");
|
||||
|
||||
=head1 TABLE: C<SchemaVersion>
|
||||
|
||||
=cut
|
||||
|
@ -33,8 +45,8 @@ __PACKAGE__->table("SchemaVersion");
|
|||
__PACKAGE__->add_columns("version", { data_type => "integer", is_nullable => 0 });
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07014 @ 2012-02-29 00:47:18
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:LFD28W0GvvrOOylCM98SEQ
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-06-13 01:54:50
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:08/7gbEQp1TqBiWFJXVY0w
|
||||
|
||||
|
||||
# You can replace this text with custom code or comments, and it will be preserved on regeneration
|
||||
|
|
|
@ -15,6 +15,18 @@ use warnings;
|
|||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
=head1 COMPONENTS LOADED
|
||||
|
||||
=over 4
|
||||
|
||||
=item * L<Hydra::Component::ToJSON>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->load_components("+Hydra::Component::ToJSON");
|
||||
|
||||
=head1 TABLE: C<SystemTypes>
|
||||
|
||||
=cut
|
||||
|
@ -56,7 +68,7 @@ __PACKAGE__->add_columns(
|
|||
__PACKAGE__->set_primary_key("system");
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07014 @ 2011-12-05 14:15:43
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:zg8db3Cbi0QOv+gLJqH8cQ
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-06-13 01:54:50
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:8cC34cEw9T3+x+7uRs4KHQ
|
||||
|
||||
1;
|
||||
|
|
|
@ -15,6 +15,18 @@ use warnings;
|
|||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
=head1 COMPONENTS LOADED
|
||||
|
||||
=over 4
|
||||
|
||||
=item * L<Hydra::Component::ToJSON>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->load_components("+Hydra::Component::ToJSON");
|
||||
|
||||
=head1 TABLE: C<UriRevMapper>
|
||||
|
||||
=cut
|
||||
|
@ -55,8 +67,8 @@ __PACKAGE__->add_columns(
|
|||
__PACKAGE__->set_primary_key("baseuri");
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07014 @ 2011-12-05 14:15:43
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:hzKzGAgAiCfU0nBOiDnjWw
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-06-13 01:54:50
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:G2GAF/Rb7cRkRegH94LwIA
|
||||
|
||||
|
||||
# You can replace this text with custom content, and it will be preserved on regeneration
|
||||
|
|
|
@ -15,6 +15,18 @@ use warnings;
|
|||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
=head1 COMPONENTS LOADED
|
||||
|
||||
=over 4
|
||||
|
||||
=item * L<Hydra::Component::ToJSON>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->load_components("+Hydra::Component::ToJSON");
|
||||
|
||||
=head1 TABLE: C<UserRoles>
|
||||
|
||||
=cut
|
||||
|
@ -75,7 +87,7 @@ __PACKAGE__->belongs_to(
|
|||
);
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-01-22 13:29:36
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:KArPHyemtnm/siwE4x5mGQ
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-06-13 01:54:50
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:aS+ivlFpndqIv8U578zz9A
|
||||
|
||||
1;
|
||||
|
|
|
@ -15,6 +15,18 @@ use warnings;
|
|||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
=head1 COMPONENTS LOADED
|
||||
|
||||
=over 4
|
||||
|
||||
=item * L<Hydra::Component::ToJSON>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->load_components("+Hydra::Component::ToJSON");
|
||||
|
||||
=head1 TABLE: C<Users>
|
||||
|
||||
=cut
|
||||
|
@ -149,8 +161,8 @@ Composing rels: L</projectmembers> -> project
|
|||
__PACKAGE__->many_to_many("projects", "projectmembers", "project");
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-01-22 13:29:36
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:OAUFl/teGpfeleb6D8FPlw
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-06-13 01:54:50
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:hy3MKvFxfL+1bTc7Hcb1zA
|
||||
# These lines were loaded from '/home/rbvermaa/src/hydra/src/lib/Hydra/Schema/Users.pm' found in @INC.
|
||||
# They are now part of the custom portion of this file
|
||||
# for you to hand-edit. If you do not either delete
|
||||
|
|
|
@ -15,6 +15,18 @@ use warnings;
|
|||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
=head1 COMPONENTS LOADED
|
||||
|
||||
=over 4
|
||||
|
||||
=item * L<Hydra::Component::ToJSON>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->load_components("+Hydra::Component::ToJSON");
|
||||
|
||||
=head1 TABLE: C<ViewJobs>
|
||||
|
||||
=cut
|
||||
|
@ -139,7 +151,7 @@ __PACKAGE__->belongs_to(
|
|||
);
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-01-22 13:29:36
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:cbSUw113ENPypbd/sICfgg
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-06-13 01:54:50
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:hz912vBfYw0rHslBPqJW2w
|
||||
|
||||
1;
|
||||
|
|
|
@ -15,6 +15,18 @@ use warnings;
|
|||
|
||||
use base 'DBIx::Class::Core';
|
||||
|
||||
=head1 COMPONENTS LOADED
|
||||
|
||||
=over 4
|
||||
|
||||
=item * L<Hydra::Component::ToJSON>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->load_components("+Hydra::Component::ToJSON");
|
||||
|
||||
=head1 TABLE: C<Views>
|
||||
|
||||
=cut
|
||||
|
@ -105,7 +117,7 @@ __PACKAGE__->has_many(
|
|||
);
|
||||
|
||||
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-01-22 13:29:36
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:Vyd2+0RAF3XGTpq3KswfAQ
|
||||
# Created by DBIx::Class::Schema::Loader v0.07033 @ 2013-06-13 01:54:50
|
||||
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:U23GZ3k5KZk2go6j2LYLHA
|
||||
|
||||
1;
|
||||
|
|
|
@ -9,6 +9,7 @@ PERL_MODULES = \
|
|||
$(wildcard Hydra/Base/*.pm) \
|
||||
$(wildcard Hydra/Base/Controller/*.pm) \
|
||||
$(wildcard Hydra/Script/*.pm) \
|
||||
$(wildcard Hydra/Component/*.pm) \
|
||||
$(wildcard Hydra/Plugin/*.pm)
|
||||
|
||||
EXTRA_DIST = \
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
[% ELSE %]
|
||||
|
||||
[% INCLUDE renderBuildList builds=queue showSchedulingInfo=1 hideResultInfo=1 %]
|
||||
[% INCLUDE renderBuildList builds=resource showSchedulingInfo=1 hideResultInfo=1 %]
|
||||
|
||||
[% END %]
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<tr><th>Machine</th><th>Job</th><th>Type</th><th>Build</th><th>Step</th><th>What</th><th>Since</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
[% FOREACH step IN steps %]
|
||||
[% FOREACH step IN resource %]
|
||||
<tr>
|
||||
<td><tt>[% IF step.machine; step.machine.match('@(.*)').0; ELSE; 'localhost'; END %]</tt></td>
|
||||
<td><tt>[% INCLUDE renderFullJobName project = step.build.project.name jobset = step.build.jobset.name job = step.build.job.name %]</tt></td>
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
[% WRAPPER makeSubMenu title="Project" %]
|
||||
<li class="nav-header">[% HTML.escape(project.name) %]</li>
|
||||
<li class="divider"></li>
|
||||
[% INCLUDE menuItem uri = c.uri_for(c.controller('Project').action_for('view'), [project.name]) title = "Overview" %]
|
||||
[% INCLUDE menuItem uri = c.uri_for(c.controller('Project').action_for('project'), [project.name]) title = "Overview" %]
|
||||
[% INCLUDE menuItem uri = c.uri_for(c.controller('Project').action_for('all'), [project.name]) title = "Latest builds" %]
|
||||
[% INCLUDE menuItem uri = c.uri_for(c.controller('Project').action_for('jobstatus'), [project.name]) title = "Job status" %]
|
||||
[% INCLUDE menuItem uri = c.uri_for(c.controller('Project').action_for('errors'), [project.name]) title = "Errors" %]
|
||||
|
@ -56,7 +56,7 @@
|
|||
<li class="nav-header">[% HTML.escape(jobset.name) %]</li>
|
||||
<li class="divider"></li>
|
||||
[% INCLUDE menuItem
|
||||
uri = c.uri_for(c.controller('Jobset').action_for('index'), [project.name, jobset.name])
|
||||
uri = c.uri_for(c.controller('Jobset').action_for('jobset'), [project.name, jobset.name])
|
||||
title = "Overview" %]
|
||||
[% INCLUDE menuItem
|
||||
uri = c.uri_for(c.controller('Jobset').action_for('evals'), [project.name, jobset.name])
|
||||
|
|
|
@ -16,4 +16,4 @@ hydra-sqlite.sql: hydra.sql
|
|||
update-dbix: hydra-sqlite.sql
|
||||
rm -f tmp.sqlite
|
||||
sqlite3 tmp.sqlite < hydra-sqlite.sql
|
||||
perl -MDBIx::Class::Schema::Loader=make_schema_at,dump_to_dir:../lib -e 'make_schema_at("Hydra::Schema", { naming => { ALL => "v5" }, relationships => 1, moniker_map => sub {return "$$_";} }, ["dbi:SQLite:tmp.sqlite"])'
|
||||
perl -I ../lib -MDBIx::Class::Schema::Loader=make_schema_at,dump_to_dir:../lib -e 'make_schema_at("Hydra::Schema", { naming => { ALL => "v5" }, relationships => 1, moniker_map => sub {return "$$_";}, components => [ "+Hydra::Component::ToJSON" ], }, ["dbi:SQLite:tmp.sqlite"])'
|
||||
|
|
12
tests/api-test.nix
Normal file
12
tests/api-test.nix
Normal file
|
@ -0,0 +1,12 @@
|
|||
let
|
||||
builder = builtins.toFile "builder.sh" ''
|
||||
echo -n ${builtins.readFile ./default.nix} > $out
|
||||
'';
|
||||
in {
|
||||
job = derivation {
|
||||
name = "job";
|
||||
system = builtins.currentSystem;
|
||||
builder = "/bin/sh";
|
||||
args = [ builder ];
|
||||
};
|
||||
}
|
63
tests/api-test.pl
Normal file
63
tests/api-test.pl
Normal file
|
@ -0,0 +1,63 @@
|
|||
use LWP::UserAgent;
|
||||
use JSON;
|
||||
use Test::Simple tests => 15;
|
||||
|
||||
my $ua = LWP::UserAgent->new;
|
||||
$ua->cookie_jar({});
|
||||
|
||||
sub request_json {
|
||||
my ($opts) = @_;
|
||||
my $req = HTTP::Request->new;
|
||||
$req->method($opts->{method} or "GET");
|
||||
$req->uri("http://localhost:3000$opts->{uri}");
|
||||
$req->header(Accept => "application/json");
|
||||
$req->content(encode_json($opts->{data})) if defined $opts->{data};
|
||||
my $res = $ua->request($req);
|
||||
print $res->as_string();
|
||||
return $res;
|
||||
}
|
||||
|
||||
my $result = request_json({ uri => "/login", method => "POST", data => { username => "root", password => "foobar" } });
|
||||
|
||||
my $user = decode_json($result->content());
|
||||
|
||||
ok($user->{username} eq "root", "The root user is named root");
|
||||
ok($user->{userroles}->[0]->{role} eq "admin", "The root user is an admin");
|
||||
|
||||
$user = decode_json(request_json({ uri => "/current-user" })->content());
|
||||
ok($user->{username} eq "root", "The current user is named root");
|
||||
ok($user->{userroles}->[0]->{role} eq "admin", "The current user is an admin");
|
||||
|
||||
ok(request_json({ uri => '/project/sample' })->code() == 404, "Non-existent projects don't exist");
|
||||
|
||||
$result = request_json({ uri => '/project/sample', method => 'PUT', data => { displayname => "Sample", enabled => "1", } });
|
||||
ok($result->code() == 201, "PUTting a new project creates it");
|
||||
|
||||
my $project = decode_json(request_json({ uri => '/project/sample' })->content());
|
||||
|
||||
ok((not @{$project->{jobsets}}), "A new project has no jobsets");
|
||||
|
||||
$result = request_json({ uri => '/jobset/sample/default', method => 'PUT', data => { nixexprpath => "default.nix", nixexprinput => "src", inputs => { src => { type => "path", values => "/run/jobset" } }, enabled => "1", checkinterval => "3600"} });
|
||||
ok($result->code() == 201, "PUTting a new jobset creates it");
|
||||
|
||||
my $jobset = decode_json(request_json({ uri => '/jobset/sample/default' })->content());
|
||||
|
||||
ok($jobset->{jobsetinputs}->[0]->{name} eq "src", "The new jobset has an 'src' input");
|
||||
ok($jobset->{jobsetinputs}->[0]->{jobsetinputalts}->[0]->{value} eq "/run/jobset", "The 'src' input is in /run/jobset");
|
||||
|
||||
system("LOGNAME=root NIX_STORE_DIR=/run/nix/store NIX_LOG_DIR=/run/nix/var/log/nix NIX_STATE_DIR=/run/nix/var/nix HYDRA_DATA=/var/lib/hydra HYDRA_DBI='dbi:Pg:dbname=hydra;user=root;' hydra-evaluator sample default");
|
||||
$result = request_json({ uri => '/jobset/sample/default/evals' });
|
||||
ok($result->code() == 200, "Can get evals of a jobset");
|
||||
my $evals = decode_json($result->content())->{evals};
|
||||
my $eval = $evals->[0];
|
||||
ok($eval->{hasnewbuilds} == 1, "The first eval of a jobset has new builds");
|
||||
|
||||
# Ugh, cached for 30s
|
||||
sleep 30;
|
||||
system("echo >> /run/jobset/default.nix; LOGNAME=root NIX_STORE_DIR=/run/nix/store NIX_LOG_DIR=/run/nix/var/log/nix NIX_STATE_DIR=/run/nix/var/nix HYDRA_DATA=/var/lib/hydra HYDRA_DBI='dbi:Pg:dbname=hydra;user=root;' hydra-evaluator sample default");
|
||||
my $evals = decode_json(request_json({ uri => '/jobset/sample/default/evals' })->content())->{evals};
|
||||
ok($evals->[0]->{jobsetevalinputs}->[0]->{revision} != $evals->[1]->{jobsetevalinputs}->[0]->{revision}, "Changing a jobset source changes its revision");
|
||||
|
||||
my $build = decode_json(request_json({ uri => "/build/" . $evals->[0]->{jobsetevalmembers}->[0]->{build} })->content());
|
||||
ok($build->{job} eq "job", "The build's job name is job");
|
||||
ok($build->{finished} == 0, "The build isn't finished yet");
|
Loading…
Reference in a new issue