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