From 002ac9ef635c7743d6a3aa24c213f8e84b3d33be Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Mon, 17 Jun 2013 12:34:21 -0400 Subject: [PATCH] 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 --- release.nix | 37 +++ src/lib/Hydra/Base/Controller/ListBuilds.pm | 4 +- src/lib/Hydra/Base/Controller/NixChannel.pm | 2 +- src/lib/Hydra/Base/Controller/REST.pm | 18 ++ src/lib/Hydra/Component/ToJSON.pm | 43 +++ src/lib/Hydra/Controller/API.pm | 7 +- src/lib/Hydra/Controller/Build.pm | 64 ++-- src/lib/Hydra/Controller/Jobset.pm | 280 +++++++++++++----- src/lib/Hydra/Controller/Project.pm | 196 ++++++++---- src/lib/Hydra/Controller/Release.pm | 4 +- src/lib/Hydra/Controller/Root.pm | 71 ++++- src/lib/Hydra/Controller/User.pm | 121 ++++++-- src/lib/Hydra/Controller/View.pm | 4 +- src/lib/Hydra/Helper/CatalystUtils.pm | 2 +- src/lib/Hydra/Schema/BuildInputs.pm | 16 +- src/lib/Hydra/Schema/BuildOutputs.pm | 16 +- src/lib/Hydra/Schema/BuildProducts.pm | 16 +- src/lib/Hydra/Schema/BuildStepOutputs.pm | 16 +- src/lib/Hydra/Schema/BuildSteps.pm | 16 +- src/lib/Hydra/Schema/Builds.pm | 16 +- src/lib/Hydra/Schema/CachedBazaarInputs.pm | 16 +- src/lib/Hydra/Schema/CachedCVSInputs.pm | 16 +- src/lib/Hydra/Schema/CachedGitInputs.pm | 16 +- src/lib/Hydra/Schema/CachedHgInputs.pm | 16 +- src/lib/Hydra/Schema/CachedPathInputs.pm | 16 +- .../Hydra/Schema/CachedSubversionInputs.pm | 16 +- src/lib/Hydra/Schema/Jobs.pm | 16 +- src/lib/Hydra/Schema/JobsetEvalInputs.pm | 16 +- src/lib/Hydra/Schema/JobsetEvalMembers.pm | 16 +- src/lib/Hydra/Schema/JobsetEvals.pm | 16 +- src/lib/Hydra/Schema/JobsetInputAlts.pm | 16 +- src/lib/Hydra/Schema/JobsetInputs.pm | 16 +- src/lib/Hydra/Schema/Jobsets.pm | 16 +- src/lib/Hydra/Schema/NewsItems.pm | 16 +- src/lib/Hydra/Schema/ProjectMembers.pm | 16 +- src/lib/Hydra/Schema/Projects.pm | 16 +- src/lib/Hydra/Schema/ReleaseMembers.pm | 16 +- src/lib/Hydra/Schema/Releases.pm | 16 +- src/lib/Hydra/Schema/SchemaVersion.pm | 16 +- src/lib/Hydra/Schema/SystemTypes.pm | 16 +- src/lib/Hydra/Schema/UriRevMapper.pm | 16 +- src/lib/Hydra/Schema/UserRoles.pm | 16 +- src/lib/Hydra/Schema/Users.pm | 16 +- src/lib/Hydra/Schema/ViewJobs.pm | 16 +- src/lib/Hydra/Schema/Views.pm | 16 +- src/lib/Makefile.am | 1 + src/root/queue.tt | 2 +- src/root/status.tt | 2 +- src/root/topbar.tt | 4 +- src/sql/Makefile.am | 2 +- tests/api-test.nix | 12 + tests/api-test.pl | 63 ++++ 52 files changed, 1163 insertions(+), 272 deletions(-) create mode 100644 src/lib/Hydra/Base/Controller/REST.pm create mode 100644 src/lib/Hydra/Component/ToJSON.pm create mode 100644 tests/api-test.nix create mode 100644 tests/api-test.pl diff --git a/release.nix b/release.nix index cb1d5b405..7b11727d0 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 dd8dafa92..6dce9c0dd 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 766c208ee..40aedaa07 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 000000000..606f0e092 --- /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 000000000..6abc48774 --- /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 7a086b4b2..48b0939a9 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 dcb17a2c4..18ca624de 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 1caffc40a..e102dbdb6 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 a43c9f9ac..c0c9145f3 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 665de3bc2..126cad6f5 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 5d4861e3d..52ffa6535 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 a013f6c16..84ec88df0 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 b21860396..1f8f78471 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 06719a973..a13f25703 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 1b9406ad2..d450fbe13 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 44b9756e3..9bd656da6 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 13050e698..538b1eff4 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 7f5607326..8eca1be3b 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 fb2249bf6..742abe35d 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 db2469101..ea27d5793 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 f2d9a0593..f3170a9ce 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 bb8a63635..646a9f4f1 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 f98eb09ce..613ddd88b 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 122941ec4..3cccd8185 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 9ce57f7b5..53716676e 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 7d92b5ce8..51dc1fa70 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 bc4e67265..6a703588c 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 bfae54508..fa68fc403 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 614c83829..5f41e4320 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 73723e186..5fcd52498 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 eed22babe..3802d9f12 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 3c2395230..48464bbfe 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 97ccdcebf..e4b4c6ad5 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 0ed5c7852..14d274285 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 5493e32e8..62b1c5425 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 fcf6e8ed7..fcc87a30f 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 516e8bede..b3b0975ba 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 7709ece50..8ff01bde6 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 778cc04c9..8a826e384 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 998b97c6c..0d68d467f 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 7d524e519..448015af8 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 a4b5ee3c9..2644da7a3 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 ff3c5afe0..2fac38ed5 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 e4992f8d3..ce32dee4a 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 192d7645c..4109f0345 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 0c27b4cc8..5c4567021 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 2e10214be..43f0a0903 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 50521b855..1742c96f4 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 36c79288a..fd9246158 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 c97bbb83d..2642ff958 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 000000000..a798294b3 --- /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 000000000..ad91d1a8e --- /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");