diff --git a/release.nix b/release.nix index cb1d5b40..7b11727d 100644 --- a/release.nix +++ b/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 { 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"); + ''; + }); } diff --git a/src/lib/Hydra/Base/Controller/ListBuilds.pm b/src/lib/Hydra/Base/Controller/ListBuilds.pm index dd8dafa9..6dce9c0d 100644 --- a/src/lib/Hydra/Base/Controller/ListBuilds.pm +++ b/src/lib/Hydra/Base/Controller/ListBuilds.pm @@ -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)); } diff --git a/src/lib/Hydra/Base/Controller/NixChannel.pm b/src/lib/Hydra/Base/Controller/NixChannel.pm index 766c208e..40aedaa0 100644 --- a/src/lib/Hydra/Base/Controller/NixChannel.pm +++ b/src/lib/Hydra/Base/Controller/NixChannel.pm @@ -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; diff --git a/src/lib/Hydra/Base/Controller/REST.pm b/src/lib/Hydra/Base/Controller/REST.pm new file mode 100644 index 00000000..606f0e09 --- /dev/null +++ b/src/lib/Hydra/Base/Controller/REST.pm @@ -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; diff --git a/src/lib/Hydra/Component/ToJSON.pm b/src/lib/Hydra/Component/ToJSON.pm new file mode 100644 index 00000000..6abc4877 --- /dev/null +++ b/src/lib/Hydra/Component/ToJSON.pm @@ -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; diff --git a/src/lib/Hydra/Controller/API.pm b/src/lib/Hydra/Controller/API.pm index 7a086b4b..48b0939a 100644 --- a/src/lib/Hydra/Controller/API.pm +++ b/src/lib/Hydra/Controller/API.pm @@ -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} } + ); } diff --git a/src/lib/Hydra/Controller/Build.pm b/src/lib/Hydra/Controller/Build.pm index dcb17a2c..18ca624d 100644 --- a/src/lib/Hydra/Controller/Build.pm +++ b/src/lib/Hydra/Controller/Build.pm @@ -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 $releaseName."; - $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"'); diff --git a/src/lib/Hydra/Controller/Jobset.pm b/src/lib/Hydra/Controller/Jobset.pm index 1caffc40..e102dbdb 100644 --- a/src/lib/Hydra/Controller/Jobset.pm +++ b/src/lib/Hydra/Controller/Jobset.pm @@ -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."); diff --git a/src/lib/Hydra/Controller/Project.pm b/src/lib/Hydra/Controller/Project.pm index a43c9f9a..c0c9145f 100644 --- a/src/lib/Hydra/Controller/Project.pm +++ b/src/lib/Hydra/Controller/Project.pm @@ -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}); diff --git a/src/lib/Hydra/Controller/Release.pm b/src/lib/Hydra/Controller/Release.pm index 665de3bc..126cad6f 100644 --- a/src/lib/Hydra/Controller/Release.pm +++ b/src/lib/Hydra/Controller/Release.pm @@ -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])); } } diff --git a/src/lib/Hydra/Controller/Root.pm b/src/lib/Hydra/Controller/Root.pm index 5d4861e3..52ffa653 100644 --- a/src/lib/Hydra/Controller/Root.pm +++ b/src/lib/Hydra/Controller/Root.pm @@ -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) = @_; diff --git a/src/lib/Hydra/Controller/User.pm b/src/lib/Hydra/Controller/User.pm index a013f6c1..84ec88df 100644 --- a/src/lib/Hydra/Controller/User.pm +++ b/src/lib/Hydra/Controller/User.pm @@ -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); + } } diff --git a/src/lib/Hydra/Controller/View.pm b/src/lib/Hydra/Controller/View.pm index b2186039..1f8f7847 100644 --- a/src/lib/Hydra/Controller/View.pm +++ b/src/lib/Hydra/Controller/View.pm @@ -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)); } } diff --git a/src/lib/Hydra/Helper/CatalystUtils.pm b/src/lib/Hydra/Helper/CatalystUtils.pm index 06719a97..a13f2570 100644 --- a/src/lib/Hydra/Helper/CatalystUtils.pm +++ b/src/lib/Hydra/Helper/CatalystUtils.pm @@ -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); diff --git a/src/lib/Hydra/Schema/BuildInputs.pm b/src/lib/Hydra/Schema/BuildInputs.pm index 1b9406ad..d450fbe1 100644 --- a/src/lib/Hydra/Schema/BuildInputs.pm +++ b/src/lib/Hydra/Schema/BuildInputs.pm @@ -15,6 +15,18 @@ use warnings; use base 'DBIx::Class::Core'; +=head1 COMPONENTS LOADED + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->load_components("+Hydra::Component::ToJSON"); + =head1 TABLE: C =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; diff --git a/src/lib/Hydra/Schema/BuildOutputs.pm b/src/lib/Hydra/Schema/BuildOutputs.pm index 44b9756e..9bd656da 100644 --- a/src/lib/Hydra/Schema/BuildOutputs.pm +++ b/src/lib/Hydra/Schema/BuildOutputs.pm @@ -15,6 +15,18 @@ use warnings; use base 'DBIx::Class::Core'; +=head1 COMPONENTS LOADED + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->load_components("+Hydra::Component::ToJSON"); + =head1 TABLE: C =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 diff --git a/src/lib/Hydra/Schema/BuildProducts.pm b/src/lib/Hydra/Schema/BuildProducts.pm index 13050e69..538b1eff 100644 --- a/src/lib/Hydra/Schema/BuildProducts.pm +++ b/src/lib/Hydra/Schema/BuildProducts.pm @@ -15,6 +15,18 @@ use warnings; use base 'DBIx::Class::Core'; +=head1 COMPONENTS LOADED + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->load_components("+Hydra::Component::ToJSON"); + =head1 TABLE: C =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; diff --git a/src/lib/Hydra/Schema/BuildStepOutputs.pm b/src/lib/Hydra/Schema/BuildStepOutputs.pm index 7f560732..8eca1be3 100644 --- a/src/lib/Hydra/Schema/BuildStepOutputs.pm +++ b/src/lib/Hydra/Schema/BuildStepOutputs.pm @@ -15,6 +15,18 @@ use warnings; use base 'DBIx::Class::Core'; +=head1 COMPONENTS LOADED + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->load_components("+Hydra::Component::ToJSON"); + =head1 TABLE: C =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 diff --git a/src/lib/Hydra/Schema/BuildSteps.pm b/src/lib/Hydra/Schema/BuildSteps.pm index fb2249bf..742abe35 100644 --- a/src/lib/Hydra/Schema/BuildSteps.pm +++ b/src/lib/Hydra/Schema/BuildSteps.pm @@ -15,6 +15,18 @@ use warnings; use base 'DBIx::Class::Core'; +=head1 COMPONENTS LOADED + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->load_components("+Hydra::Component::ToJSON"); + =head1 TABLE: C =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; diff --git a/src/lib/Hydra/Schema/Builds.pm b/src/lib/Hydra/Schema/Builds.pm index db246910..ea27d579 100644 --- a/src/lib/Hydra/Schema/Builds.pm +++ b/src/lib/Hydra/Schema/Builds.pm @@ -15,6 +15,18 @@ use warnings; use base 'DBIx::Class::Core'; +=head1 COMPONENTS LOADED + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->load_components("+Hydra::Component::ToJSON"); + =head1 TABLE: C =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", diff --git a/src/lib/Hydra/Schema/CachedBazaarInputs.pm b/src/lib/Hydra/Schema/CachedBazaarInputs.pm index f2d9a059..f3170a9c 100644 --- a/src/lib/Hydra/Schema/CachedBazaarInputs.pm +++ b/src/lib/Hydra/Schema/CachedBazaarInputs.pm @@ -15,6 +15,18 @@ use warnings; use base 'DBIx::Class::Core'; +=head1 COMPONENTS LOADED + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->load_components("+Hydra::Component::ToJSON"); + =head1 TABLE: C =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 diff --git a/src/lib/Hydra/Schema/CachedCVSInputs.pm b/src/lib/Hydra/Schema/CachedCVSInputs.pm index bb8a6363..646a9f4f 100644 --- a/src/lib/Hydra/Schema/CachedCVSInputs.pm +++ b/src/lib/Hydra/Schema/CachedCVSInputs.pm @@ -15,6 +15,18 @@ use warnings; use base 'DBIx::Class::Core'; +=head1 COMPONENTS LOADED + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->load_components("+Hydra::Component::ToJSON"); + =head1 TABLE: C =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; diff --git a/src/lib/Hydra/Schema/CachedGitInputs.pm b/src/lib/Hydra/Schema/CachedGitInputs.pm index f98eb09c..613ddd88 100644 --- a/src/lib/Hydra/Schema/CachedGitInputs.pm +++ b/src/lib/Hydra/Schema/CachedGitInputs.pm @@ -15,6 +15,18 @@ use warnings; use base 'DBIx::Class::Core'; +=head1 COMPONENTS LOADED + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->load_components("+Hydra::Component::ToJSON"); + =head1 TABLE: C =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; diff --git a/src/lib/Hydra/Schema/CachedHgInputs.pm b/src/lib/Hydra/Schema/CachedHgInputs.pm index 122941ec..3cccd818 100644 --- a/src/lib/Hydra/Schema/CachedHgInputs.pm +++ b/src/lib/Hydra/Schema/CachedHgInputs.pm @@ -15,6 +15,18 @@ use warnings; use base 'DBIx::Class::Core'; +=head1 COMPONENTS LOADED + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->load_components("+Hydra::Component::ToJSON"); + =head1 TABLE: C =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 diff --git a/src/lib/Hydra/Schema/CachedPathInputs.pm b/src/lib/Hydra/Schema/CachedPathInputs.pm index 9ce57f7b..53716676 100644 --- a/src/lib/Hydra/Schema/CachedPathInputs.pm +++ b/src/lib/Hydra/Schema/CachedPathInputs.pm @@ -15,6 +15,18 @@ use warnings; use base 'DBIx::Class::Core'; +=head1 COMPONENTS LOADED + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->load_components("+Hydra::Component::ToJSON"); + =head1 TABLE: C =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; diff --git a/src/lib/Hydra/Schema/CachedSubversionInputs.pm b/src/lib/Hydra/Schema/CachedSubversionInputs.pm index 7d92b5ce..51dc1fa7 100644 --- a/src/lib/Hydra/Schema/CachedSubversionInputs.pm +++ b/src/lib/Hydra/Schema/CachedSubversionInputs.pm @@ -15,6 +15,18 @@ use warnings; use base 'DBIx::Class::Core'; +=head1 COMPONENTS LOADED + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->load_components("+Hydra::Component::ToJSON"); + =head1 TABLE: C =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; diff --git a/src/lib/Hydra/Schema/Jobs.pm b/src/lib/Hydra/Schema/Jobs.pm index bc4e6726..6a703588 100644 --- a/src/lib/Hydra/Schema/Jobs.pm +++ b/src/lib/Hydra/Schema/Jobs.pm @@ -15,6 +15,18 @@ use warnings; use base 'DBIx::Class::Core'; +=head1 COMPONENTS LOADED + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->load_components("+Hydra::Component::ToJSON"); + =head1 TABLE: C =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; diff --git a/src/lib/Hydra/Schema/JobsetEvalInputs.pm b/src/lib/Hydra/Schema/JobsetEvalInputs.pm index bfae5450..fa68fc40 100644 --- a/src/lib/Hydra/Schema/JobsetEvalInputs.pm +++ b/src/lib/Hydra/Schema/JobsetEvalInputs.pm @@ -15,6 +15,18 @@ use warnings; use base 'DBIx::Class::Core'; +=head1 COMPONENTS LOADED + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->load_components("+Hydra::Component::ToJSON"); + =head1 TABLE: C =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 diff --git a/src/lib/Hydra/Schema/JobsetEvalMembers.pm b/src/lib/Hydra/Schema/JobsetEvalMembers.pm index 614c8382..5f41e432 100644 --- a/src/lib/Hydra/Schema/JobsetEvalMembers.pm +++ b/src/lib/Hydra/Schema/JobsetEvalMembers.pm @@ -15,6 +15,18 @@ use warnings; use base 'DBIx::Class::Core'; +=head1 COMPONENTS LOADED + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->load_components("+Hydra::Component::ToJSON"); + =head1 TABLE: C =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 diff --git a/src/lib/Hydra/Schema/JobsetEvals.pm b/src/lib/Hydra/Schema/JobsetEvals.pm index 73723e18..5fcd5249 100644 --- a/src/lib/Hydra/Schema/JobsetEvals.pm +++ b/src/lib/Hydra/Schema/JobsetEvals.pm @@ -15,6 +15,18 @@ use warnings; use base 'DBIx::Class::Core'; +=head1 COMPONENTS LOADED + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->load_components("+Hydra::Component::ToJSON"); + =head1 TABLE: C =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", diff --git a/src/lib/Hydra/Schema/JobsetInputAlts.pm b/src/lib/Hydra/Schema/JobsetInputAlts.pm index eed22bab..3802d9f1 100644 --- a/src/lib/Hydra/Schema/JobsetInputAlts.pm +++ b/src/lib/Hydra/Schema/JobsetInputAlts.pm @@ -15,6 +15,18 @@ use warnings; use base 'DBIx::Class::Core'; +=head1 COMPONENTS LOADED + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->load_components("+Hydra::Component::ToJSON"); + =head1 TABLE: C =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; diff --git a/src/lib/Hydra/Schema/JobsetInputs.pm b/src/lib/Hydra/Schema/JobsetInputs.pm index 3c239523..48464bbf 100644 --- a/src/lib/Hydra/Schema/JobsetInputs.pm +++ b/src/lib/Hydra/Schema/JobsetInputs.pm @@ -15,6 +15,18 @@ use warnings; use base 'DBIx::Class::Core'; +=head1 COMPONENTS LOADED + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->load_components("+Hydra::Component::ToJSON"); + =head1 TABLE: C =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; diff --git a/src/lib/Hydra/Schema/Jobsets.pm b/src/lib/Hydra/Schema/Jobsets.pm index 97ccdceb..e4b4c6ad 100644 --- a/src/lib/Hydra/Schema/Jobsets.pm +++ b/src/lib/Hydra/Schema/Jobsets.pm @@ -15,6 +15,18 @@ use warnings; use base 'DBIx::Class::Core'; +=head1 COMPONENTS LOADED + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->load_components("+Hydra::Component::ToJSON"); + =head1 TABLE: C =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; diff --git a/src/lib/Hydra/Schema/NewsItems.pm b/src/lib/Hydra/Schema/NewsItems.pm index 0ed5c785..14d27428 100644 --- a/src/lib/Hydra/Schema/NewsItems.pm +++ b/src/lib/Hydra/Schema/NewsItems.pm @@ -15,6 +15,18 @@ use warnings; use base 'DBIx::Class::Core'; +=head1 COMPONENTS LOADED + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->load_components("+Hydra::Component::ToJSON"); + =head1 TABLE: C =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; diff --git a/src/lib/Hydra/Schema/ProjectMembers.pm b/src/lib/Hydra/Schema/ProjectMembers.pm index 5493e32e..62b1c542 100644 --- a/src/lib/Hydra/Schema/ProjectMembers.pm +++ b/src/lib/Hydra/Schema/ProjectMembers.pm @@ -15,6 +15,18 @@ use warnings; use base 'DBIx::Class::Core'; +=head1 COMPONENTS LOADED + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->load_components("+Hydra::Component::ToJSON"); + =head1 TABLE: C =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 diff --git a/src/lib/Hydra/Schema/Projects.pm b/src/lib/Hydra/Schema/Projects.pm index fcf6e8ed..fcc87a30 100644 --- a/src/lib/Hydra/Schema/Projects.pm +++ b/src/lib/Hydra/Schema/Projects.pm @@ -15,6 +15,18 @@ use warnings; use base 'DBIx::Class::Core'; +=head1 COMPONENTS LOADED + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->load_components("+Hydra::Component::ToJSON"); + =head1 TABLE: C =cut @@ -255,8 +267,8 @@ Composing rels: L -> 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 diff --git a/src/lib/Hydra/Schema/ReleaseMembers.pm b/src/lib/Hydra/Schema/ReleaseMembers.pm index 516e8bed..b3b0975b 100644 --- a/src/lib/Hydra/Schema/ReleaseMembers.pm +++ b/src/lib/Hydra/Schema/ReleaseMembers.pm @@ -15,6 +15,18 @@ use warnings; use base 'DBIx::Class::Core'; +=head1 COMPONENTS LOADED + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->load_components("+Hydra::Component::ToJSON"); + =head1 TABLE: C =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; diff --git a/src/lib/Hydra/Schema/Releases.pm b/src/lib/Hydra/Schema/Releases.pm index 7709ece5..8ff01bde 100644 --- a/src/lib/Hydra/Schema/Releases.pm +++ b/src/lib/Hydra/Schema/Releases.pm @@ -15,6 +15,18 @@ use warnings; use base 'DBIx::Class::Core'; +=head1 COMPONENTS LOADED + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->load_components("+Hydra::Component::ToJSON"); + =head1 TABLE: C =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; diff --git a/src/lib/Hydra/Schema/SchemaVersion.pm b/src/lib/Hydra/Schema/SchemaVersion.pm index 778cc04c..8a826e38 100644 --- a/src/lib/Hydra/Schema/SchemaVersion.pm +++ b/src/lib/Hydra/Schema/SchemaVersion.pm @@ -15,6 +15,18 @@ use warnings; use base 'DBIx::Class::Core'; +=head1 COMPONENTS LOADED + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->load_components("+Hydra::Component::ToJSON"); + =head1 TABLE: C =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 diff --git a/src/lib/Hydra/Schema/SystemTypes.pm b/src/lib/Hydra/Schema/SystemTypes.pm index 998b97c6..0d68d467 100644 --- a/src/lib/Hydra/Schema/SystemTypes.pm +++ b/src/lib/Hydra/Schema/SystemTypes.pm @@ -15,6 +15,18 @@ use warnings; use base 'DBIx::Class::Core'; +=head1 COMPONENTS LOADED + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->load_components("+Hydra::Component::ToJSON"); + =head1 TABLE: C =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; diff --git a/src/lib/Hydra/Schema/UriRevMapper.pm b/src/lib/Hydra/Schema/UriRevMapper.pm index 7d524e51..448015af 100644 --- a/src/lib/Hydra/Schema/UriRevMapper.pm +++ b/src/lib/Hydra/Schema/UriRevMapper.pm @@ -15,6 +15,18 @@ use warnings; use base 'DBIx::Class::Core'; +=head1 COMPONENTS LOADED + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->load_components("+Hydra::Component::ToJSON"); + =head1 TABLE: C =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 diff --git a/src/lib/Hydra/Schema/UserRoles.pm b/src/lib/Hydra/Schema/UserRoles.pm index a4b5ee3c..2644da7a 100644 --- a/src/lib/Hydra/Schema/UserRoles.pm +++ b/src/lib/Hydra/Schema/UserRoles.pm @@ -15,6 +15,18 @@ use warnings; use base 'DBIx::Class::Core'; +=head1 COMPONENTS LOADED + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->load_components("+Hydra::Component::ToJSON"); + =head1 TABLE: C =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; diff --git a/src/lib/Hydra/Schema/Users.pm b/src/lib/Hydra/Schema/Users.pm index ff3c5afe..2fac38ed 100644 --- a/src/lib/Hydra/Schema/Users.pm +++ b/src/lib/Hydra/Schema/Users.pm @@ -15,6 +15,18 @@ use warnings; use base 'DBIx::Class::Core'; +=head1 COMPONENTS LOADED + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->load_components("+Hydra::Component::ToJSON"); + =head1 TABLE: C =cut @@ -149,8 +161,8 @@ Composing rels: L -> 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 diff --git a/src/lib/Hydra/Schema/ViewJobs.pm b/src/lib/Hydra/Schema/ViewJobs.pm index e4992f8d..ce32dee4 100644 --- a/src/lib/Hydra/Schema/ViewJobs.pm +++ b/src/lib/Hydra/Schema/ViewJobs.pm @@ -15,6 +15,18 @@ use warnings; use base 'DBIx::Class::Core'; +=head1 COMPONENTS LOADED + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->load_components("+Hydra::Component::ToJSON"); + =head1 TABLE: C =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; diff --git a/src/lib/Hydra/Schema/Views.pm b/src/lib/Hydra/Schema/Views.pm index 192d7645..4109f034 100644 --- a/src/lib/Hydra/Schema/Views.pm +++ b/src/lib/Hydra/Schema/Views.pm @@ -15,6 +15,18 @@ use warnings; use base 'DBIx::Class::Core'; +=head1 COMPONENTS LOADED + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->load_components("+Hydra::Component::ToJSON"); + =head1 TABLE: C =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; diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index 0c27b4cc..5c456702 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -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 = \ diff --git a/src/root/queue.tt b/src/root/queue.tt index 2e10214b..43f0a090 100644 --- a/src/root/queue.tt +++ b/src/root/queue.tt @@ -7,7 +7,7 @@ [% ELSE %] - [% INCLUDE renderBuildList builds=queue showSchedulingInfo=1 hideResultInfo=1 %] + [% INCLUDE renderBuildList builds=resource showSchedulingInfo=1 hideResultInfo=1 %] [% END %] diff --git a/src/root/status.tt b/src/root/status.tt index 50521b85..1742c96f 100644 --- a/src/root/status.tt +++ b/src/root/status.tt @@ -6,7 +6,7 @@ MachineJobTypeBuildStepWhatSince - [% FOREACH step IN steps %] + [% FOREACH step IN resource %] [% IF step.machine; step.machine.match('@(.*)').0; ELSE; 'localhost'; END %] [% INCLUDE renderFullJobName project = step.build.project.name jobset = step.build.jobset.name job = step.build.job.name %] diff --git a/src/root/topbar.tt b/src/root/topbar.tt index 36c79288..fd924615 100644 --- a/src/root/topbar.tt +++ b/src/root/topbar.tt @@ -37,7 +37,7 @@ [% WRAPPER makeSubMenu title="Project" %]
  • - [% 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 @@
  • [% 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]) diff --git a/src/sql/Makefile.am b/src/sql/Makefile.am index c97bbb83..2642ff95 100644 --- a/src/sql/Makefile.am +++ b/src/sql/Makefile.am @@ -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"])' diff --git a/tests/api-test.nix b/tests/api-test.nix new file mode 100644 index 00000000..a798294b --- /dev/null +++ b/tests/api-test.nix @@ -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 ]; + }; +} diff --git a/tests/api-test.pl b/tests/api-test.pl new file mode 100644 index 00000000..ad91d1a8 --- /dev/null +++ b/tests/api-test.pl @@ -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");