2008-11-25 11:01:42 +00:00
package Hydra::Controller::Root ;
2008-10-28 10:19:31 +00:00
use strict ;
use warnings ;
2009-03-04 10:59:14 +00:00
use base 'Hydra::Base::Controller::ListBuilds' ;
2008-11-25 11:01:42 +00:00
use Hydra::Helper::Nix ;
2009-02-25 12:03:13 +00:00
use Hydra::Helper::CatalystUtils ;
2010-12-03 09:40:25 +00:00
use Digest::SHA1 qw( sha1_hex ) ;
2011-12-01 20:46:02 -05:00
use Nix::Store ;
2012-07-30 20:26:34 +00:00
use Nix::Config ;
2009-02-25 10:52:41 +00:00
# Put this controller at top-level.
2008-10-28 10:19:31 +00:00
__PACKAGE__ - > config - > { namespace } = '' ;
2008-11-13 09:25:38 +00:00
sub begin :Private {
2010-08-31 15:27:46 +00:00
my ( $ self , $ c , @ args ) = @ _ ;
2008-11-13 09:48:10 +00:00
$ c - > stash - > { curUri } = $ c - > request - > uri ;
2009-03-23 13:52:24 +00:00
$ c - > stash - > { version } = $ ENV { "HYDRA_RELEASE" } || "<devel>" ;
2011-04-01 07:40:06 +00:00
$ c - > stash - > { nixVersion } = $ ENV { "NIX_RELEASE" } || "<devel>" ;
2010-08-31 15:27:46 +00:00
$ c - > stash - > { curTime } = time ;
2012-04-13 11:47:05 +02:00
$ c - > stash - > { logo } = $ ENV { "HYDRA_LOGO" } ? "/logo" : "" ;
2013-02-27 18:33:47 +01:00
$ c - > stash - > { tracker } = $ ENV { "HYDRA_TRACKER" } ;
$ c - > stash - > { flashMsg } = $ c - > flash - > { flashMsg } ;
$ c - > stash - > { successMsg } = $ c - > flash - > { successMsg } ;
2011-04-19 12:00:54 +00:00
2010-08-31 15:37:50 +00:00
if ( scalar ( @ args ) == 0 || $ args [ 0 ] ne "static" ) {
2013-01-22 14:09:37 +01:00
$ c - > stash - > { nrRunningBuilds } = $ c - > model ( 'DB::Builds' ) - > search ( { finished = > 0 , busy = > 1 } , { } ) - > count ( ) ;
$ c - > stash - > { nrQueuedBuilds } = $ c - > model ( 'DB::Builds' ) - > search ( { finished = > 0 } ) - > count ( ) ;
2010-08-31 15:27:46 +00:00
}
2013-05-25 15:36:58 -04:00
# Gather the supported input types.
$ c - > stash - > { inputTypes } = {
'string' = > 'String value' ,
'boolean' = > 'Boolean' ,
'build' = > 'Build output' ,
'sysbuild' = > 'Build output (same system)'
} ;
$ _ - > supportedInputTypes ( $ c - > stash - > { inputTypes } ) foreach @ { $ c - > hydra_plugins } ;
2013-06-17 12:34:21 -04:00
$ 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 ;
}
2008-11-13 09:25:38 +00:00
}
2013-06-17 12:34:21 -04:00
sub deserialize :ActionClass('Deserialize') { }
2008-11-13 09:25:38 +00:00
2008-10-28 10:19:31 +00:00
sub index :Path :Args(0) {
2008-11-26 17:43:45 +00:00
my ( $ self , $ c ) = @ _ ;
2009-04-02 16:15:57 +00:00
$ c - > stash - > { template } = 'overview.tt' ;
2010-06-04 14:43:28 +00:00
$ c - > stash - > { projects } = [ $ c - > model ( 'DB::Projects' ) - > search ( isAdmin ( $ c ) ? { } : { hidden = > 0 } , { order_by = > 'name' } ) ] ;
2010-04-27 13:29:08 +00:00
$ c - > stash - > { newsItems } = [ $ c - > model ( 'DB::NewsItems' ) - > search ( { } , { order_by = > [ 'createtime DESC' ] , rows = > 5 } ) ] ;
2013-06-17 12:34:21 -04:00
$ self - > status_ok (
$ c ,
entity = > [ $ c - > model ( 'DB::Projects' ) - > search ( isAdmin ( $ c ) ? { } : { hidden = > 0 } , {
order_by = > 'name' ,
columns = > [ 'name' , 'displayname' ]
} ) ]
) ;
2008-10-28 10:19:31 +00:00
}
2013-06-17 12:34:21 -04:00
sub queue :Local :Args(0) :ActionClass('REST') { }
sub queue_GET {
2008-11-26 19:48:04 +00:00
my ( $ self , $ c ) = @ _ ;
2008-11-26 17:43:45 +00:00
$ c - > stash - > { template } = 'queue.tt' ;
2013-02-27 18:33:47 +01:00
$ c - > stash - > { flashMsg } // = $ c - > flash - > { buildMsg } ;
2013-06-17 12:34:21 -04:00
$ 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' ] } ) ]
) ;
2008-11-26 17:43:45 +00:00
}
2010-08-31 15:27:46 +00:00
2009-12-01 19:15:09 +00:00
sub timeline :Local {
my ( $ self , $ c ) = @ _ ;
2009-12-01 19:17:38 +00:00
my $ pit = time ( ) ;
2011-04-01 07:40:06 +00:00
$ c - > stash - > { pit } = $ pit ;
2009-12-01 19:17:38 +00:00
$ pit = $ pit - ( 24 * 60 * 60 ) - 1 ;
2009-12-01 19:15:09 +00:00
$ c - > stash - > { template } = 'timeline.tt' ;
2012-03-05 21:52:47 +01:00
$ c - > stash - > { builds } = [ $ c - > model ( 'DB::Builds' ) - > search
( { finished = > 1 , stoptime = > { '>' = > $ pit } }
2013-01-22 14:41:02 +01:00
, { order_by = > [ "starttime" ] }
2012-03-05 21:52:47 +01:00
) ] ;
2009-12-01 19:15:09 +00:00
}
2008-11-26 17:43:45 +00:00
2010-08-31 15:27:46 +00:00
2013-06-17 12:34:21 -04:00
sub status :Local :Args(0) :ActionClass('REST') { }
sub status_GET {
2010-08-31 15:27:46 +00:00
my ( $ self , $ c ) = @ _ ;
2013-06-17 12:34:21 -04:00
$ 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' ]
}
) ]
) ;
2010-08-31 15:27:46 +00:00
}
2013-02-20 16:40:09 +01:00
sub machines :Local Args(0) {
my ( $ self , $ c ) = @ _ ;
2013-03-04 15:37:20 -05:00
my $ machines = getMachines ;
2013-04-23 15:20:24 +02:00
# Add entry for localhost.
$ { $ machines } { '' } // = { } ;
# Get the last finished build step for each machine.
foreach my $ m ( keys % { $ machines } ) {
my $ idle = $ c - > model ( 'DB::BuildSteps' ) - > find (
{ machine = > "$m" , stoptime = > { '!=' , undef } } ,
{ order_by = > 'stoptime desc' , rows = > 1 } ) ;
$ { $ machines } { $ m } { 'idle' } = $ idle ? $ idle - > stoptime : 0 ;
2013-03-04 15:37:20 -05:00
}
2013-04-23 15:20:24 +02:00
2013-03-04 15:37:20 -05:00
$ c - > stash - > { machines } = $ machines ;
2013-02-20 16:40:09 +01:00
$ c - > stash - > { steps } = [ $ c - > model ( 'DB::BuildSteps' ) - > search (
{ finished = > 0 , 'me.busy' = > 1 , 'build.busy' = > 1 , } ,
{ join = > [ 'build' ]
, order_by = > [ 'machine' , 'stepnr' ]
} ) ] ;
$ c - > stash - > { template } = 'machine-status.tt' ;
}
2009-03-04 10:59:14 +00:00
# Hydra::Base::Controller::ListBuilds needs this.
sub get_builds : Chained('/') PathPart('') CaptureArgs(0) {
my ( $ self , $ c ) = @ _ ;
$ c - > stash - > { allBuilds } = $ c - > model ( 'DB::Builds' ) ;
2009-04-03 15:37:21 +00:00
$ c - > stash - > { jobStatus } = $ c - > model ( 'DB' ) - > resultset ( 'JobStatus' ) ;
2009-04-08 22:08:00 +00:00
$ c - > stash - > { allJobsets } = $ c - > model ( 'DB::Jobsets' ) ;
$ c - > stash - > { allJobs } = $ c - > model ( 'DB::Jobs' ) ;
2009-04-03 15:37:21 +00:00
$ c - > stash - > { latestSucceeded } = $ c - > model ( 'DB' ) - > resultset ( 'LatestSucceeded' ) ;
2009-03-04 16:36:23 +00:00
$ c - > stash - > { channelBaseName } = "everything" ;
2009-03-04 10:59:14 +00:00
}
2009-03-31 13:48:03 +00:00
sub robots_txt : Path('robots.txt') {
my ( $ self , $ c ) = @ _ ;
2009-03-31 14:14:45 +00:00
sub uri_for {
2013-01-23 12:41:57 +00:00
my ( $ c , $ controller , $ action , @ args ) = @ _ ;
2009-03-31 14:14:45 +00:00
return $ c - > uri_for ( $ c - > controller ( $ controller ) - > action_for ( $ action ) , @ args ) - > path ;
}
sub channelUris {
2013-01-23 12:41:57 +00:00
my ( $ c , $ controller , $ bindings ) = @ _ ;
2009-03-31 14:14:45 +00:00
return
2013-01-23 12:41:57 +00:00
( uri_for ( $ c , $ controller , 'closure' , $ bindings , "*" )
, uri_for ( $ c , $ controller , 'manifest' , $ bindings )
, uri_for ( $ c , $ controller , 'pkg' , $ bindings , "*" )
, uri_for ( $ c , $ controller , 'nixexprs' , $ bindings )
, uri_for ( $ c , $ controller , 'channel_contents' , $ bindings )
2009-03-31 14:14:45 +00:00
) ;
}
2009-03-31 13:48:03 +00:00
# Put actions that are expensive or not useful for indexing in
# robots.txt. Note: wildcards are not universally supported in
# robots.txt, but apparently Google supports them.
my @ rules =
2013-01-23 12:41:57 +00:00
( uri_for ( $ c , 'Build' , 'deps' , [ "*" ] )
, uri_for ( $ c , 'Build' , 'view_nixlog' , [ "*" ] , "*" )
, uri_for ( $ c , 'Build' , 'view_log' , [ "*" ] , "*" )
, uri_for ( $ c , 'Build' , 'view_log' , [ "*" ] )
, uri_for ( $ c , 'Build' , 'download' , [ "*" ] , "*" )
, uri_for ( $ c , 'Root' , 'nar' , [] , "*" )
, uri_for ( $ c , 'Root' , 'status' , [] )
, uri_for ( $ c , 'Root' , 'all' , [] )
, uri_for ( $ c , 'API' , 'scmdiff' , [] )
, uri_for ( $ c , 'API' , 'logdiff' , [] , "*" , "*" )
, uri_for ( $ c , 'Project' , 'all' , [ "*" ] )
, channelUris ( $ c , 'Root' , [ "*" ] )
, channelUris ( $ c , 'Project' , [ "*" , "*" ] )
, channelUris ( $ c , 'Jobset' , [ "*" , "*" , "*" ] )
, channelUris ( $ c , 'Job' , [ "*" , "*" , "*" , "*" ] )
, channelUris ( $ c , 'Build' , [ "*" ] )
2009-03-31 13:48:03 +00:00
) ;
2011-04-01 07:40:06 +00:00
2009-03-31 14:55:47 +00:00
$ c - > stash - > { 'plain' } = { data = > "User-agent: *\n" . join ( '' , map { "Disallow: $_\n" } @ rules ) } ;
2009-03-31 13:48:03 +00:00
$ c - > forward ( 'Hydra::View::Plain' ) ;
}
2011-04-01 07:40:06 +00:00
2008-10-28 10:19:31 +00:00
sub default :Path {
2008-11-26 17:43:45 +00:00
my ( $ self , $ c ) = @ _ ;
2009-02-25 14:34:29 +00:00
notFound ( $ c , "Page not found." ) ;
2009-02-13 17:35:54 +00:00
}
2009-02-25 10:52:41 +00:00
sub end : ActionClass('RenderView') {
my ( $ self , $ c ) = @ _ ;
2013-03-04 15:25:23 +01:00
if ( defined $ c - > stash - > { json } ) {
if ( scalar @ { $ c - > error } ) {
$ c - > stash - > { json } - > { error } = join "\n" , @ { $ c - > error } ;
$ c - > clear_errors ;
}
$ c - > forward ( 'View::JSON' ) ;
}
2013-06-17 12:34:21 -04:00
if ( scalar @ { $ c - > error } ) {
$ c - > stash - > { resource } = { errors = > "$c->error" } ;
2009-02-25 10:52:41 +00:00
$ c - > stash - > { template } = 'error.tt' ;
$ c - > stash - > { errors } = $ c - > error ;
2013-02-22 14:27:38 +01:00
$ c - > response - > status ( 500 ) if $ c - > response - > status == 200 ;
2009-02-25 16:29:54 +00:00
if ( $ c - > response - > status >= 300 ) {
$ c - > stash - > { httpStatus } =
$ c - > response - > status . " " . HTTP::Status:: status_message ( $ c - > response - > status ) ;
}
2009-02-25 10:52:41 +00:00
$ c - > clear_errors ;
2013-06-17 12:34:21 -04:00
} 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 ) ;
2009-02-25 10:52:41 +00:00
}
2013-06-17 12:34:21 -04:00
$ c - > forward ( 'serialize' ) ;
2009-02-25 10:52:41 +00:00
}
2008-10-28 10:19:31 +00:00
2013-06-17 12:34:21 -04:00
sub serialize : ActionClass('Serialize') { }
2008-10-28 10:19:31 +00:00
2010-06-22 12:00:19 +00:00
sub nar :Local :Args(1) {
my ( $ self , $ c , $ path ) = @ _ ;
2011-03-23 13:03:40 +00:00
$ path = ( $ ENV { NIX_STORE_DIR } || "/nix/store" ) . "/$path" ;
2010-06-22 12:00:19 +00:00
if ( ! isValidPath ( $ path ) ) {
$ c - > response - > status ( 410 ) ; # "Gone"
error ( $ c , "Path " . $ path . " is no longer available." ) ;
}
$ c - > stash - > { current_view } = 'NixNAR' ;
$ c - > stash - > { storePath } = $ path ;
}
2012-07-02 15:18:30 +00:00
2012-07-30 20:26:34 +00:00
sub nix_cache_info :Path('nix-cache-info') :Args(0) {
my ( $ self , $ c ) = @ _ ;
2012-08-01 18:00:55 +00:00
$ c - > response - > content_type ( 'text/plain' ) ;
2013-04-23 15:39:05 +02:00
$ c - > stash - > { plain } - > { data } =
2012-07-30 20:26:34 +00:00
#"StoreDir: $Nix::Config::storeDir\n" . # FIXME
"StoreDir: /nix/store\n" .
2013-01-22 14:09:37 +01:00
"WantMassQuery: 0\n" .
2012-11-06 17:13:17 +01:00
# Give Hydra binary caches a very low priority (lower than the
# static binary cache http://nixos.org/binary-cache).
2013-04-23 15:39:05 +02:00
"Priority: 100\n" ;
2012-07-30 20:26:34 +00:00
$ c - > forward ( 'Hydra::View::Plain' ) ;
}
2012-07-02 20:09:45 +02:00
sub narinfo :LocalRegex('^([a-z0-9]+).narinfo$') :Args(0) {
2012-07-02 15:18:30 +00:00
my ( $ self , $ c ) = @ _ ;
2012-07-02 20:09:45 +02:00
my $ hash = $ c - > req - > captures - > [ 0 ] ;
2013-04-23 15:33:58 +02:00
die if length ( $ hash ) != 32 ;
my $ path = queryPathFromHashPart ( $ hash ) ;
if ( ! $ path ) {
$ c - > response - > content_type ( 'text/plain' ) ;
2013-04-23 15:39:05 +02:00
$ c - > stash - > { plain } - > { data } = "does not exist\n" ;
2013-04-23 15:33:58 +02:00
$ c - > forward ( 'Hydra::View::Plain' ) ;
2013-04-30 16:23:19 +02:00
return ;
2013-04-23 15:33:58 +02:00
}
2013-04-30 16:23:19 +02:00
2013-04-23 15:33:58 +02:00
$ c - > stash - > { storePath } = $ path ;
2013-04-23 15:39:05 +02:00
$ c - > forward ( 'Hydra::View::NARInfo' ) ;
2012-07-02 15:18:30 +00:00
}
2011-04-18 08:21:27 +00:00
sub logo :Local {
my ( $ self , $ c ) = @ _ ;
my $ path = $ ENV { "HYDRA_LOGO" } or die ( "Logo not set!" ) ;
$ c - > serve_static_file ( $ path ) ;
}
2013-02-21 17:27:17 +01:00
sub evals :Local Args(0) {
my ( $ self , $ c ) = @ _ ;
$ c - > stash - > { template } = 'evals.tt' ;
my $ page = int ( $ c - > req - > param ( 'page' ) || "1" ) || 1 ;
my $ resultsPerPage = 20 ;
my $ evals = $ c - > model ( 'DB::JobsetEvals' ) ;
$ 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 )
}
2013-02-22 15:45:10 +01:00
sub search :Local Args(0) {
my ( $ self , $ c ) = @ _ ;
$ c - > stash - > { template } = 'search.tt' ;
my $ query = trim $ c - > request - > params - > { "query" } ;
error ( $ c , "Query is empty." ) if $ query eq "" ;
error ( $ c , "Invalid character in query." )
2013-04-25 09:57:30 -04:00
unless $ query =~ /^[a-zA-Z0-9_\-\/.]+$/ ;
2013-02-22 15:45:10 +01:00
2013-02-22 16:41:42 +01:00
$ c - > stash - > { limit } = 500 ;
2013-02-22 15:45:10 +01:00
$ c - > stash - > { projects } = [ $ c - > model ( 'DB::Projects' ) - > search (
2013-02-22 15:56:29 +01:00
{ - and = >
[ { - or = > [ name = > { ilike = > "%$query%" } , displayName = > { ilike = > "%$query%" } , description = > { ilike = > "%$query%" } ] }
, { hidden = > 0 }
]
} ,
2013-02-22 15:45:10 +01:00
{ order_by = > [ "name" ] } ) ] ;
$ c - > stash - > { jobsets } = [ $ c - > model ( 'DB::Jobsets' ) - > search (
2013-02-22 15:56:29 +01:00
{ - and = >
[ { - or = > [ "me.name" = > { ilike = > "%$query%" } , "me.description" = > { ilike = > "%$query%" } ] }
, { "project.hidden" = > 0 , "me.hidden" = > 0 }
]
} ,
{ order_by = > [ "project" , "name" ] , join = > [ "project" ] } ) ] ;
2013-02-22 15:45:10 +01:00
$ c - > stash - > { jobs } = [ $ c - > model ( 'DB::Jobs' ) - > search (
2013-02-22 15:56:29 +01:00
{ "me.name" = > { ilike = > "%$query%" }
, "project.hidden" = > 0
, "jobset.hidden" = > 0
} ,
2013-02-22 16:21:50 +01:00
{ order_by = > [ "enabled_ desc" , "project" , "jobset" , "name" ] , join = > [ "project" , "jobset" ]
2013-04-01 20:18:00 -04:00
, "+select" = > [ \ "(project.enabled = 1 and jobset.enabled = 1 and exists (select 1 from Builds where project = project.name and jobset = jobset.name and job = me.name and iscurrent = 1)) as enabled_" ]
2013-02-22 16:21:50 +01:00
, "+as" = > [ "enabled" ]
2013-02-22 16:41:42 +01:00
, rows = > $ c - > stash - > { limit } + 1
2013-02-22 16:21:50 +01:00
} ) ] ;
2013-04-25 09:57:30 -04:00
2013-04-26 08:51:49 -04:00
# Perform build search in separate queries to prevent seq scan on buildoutputs table.
2013-04-25 09:57:30 -04:00
$ c - > stash - > { builds } = [ $ c - > model ( 'DB::Builds' ) - > search (
2013-04-26 08:51:49 -04:00
{ "buildoutputs.path" = > trim ( $ query ) } ,
2013-04-25 09:57:30 -04:00
{ order_by = > [ "id desc" ] , join = > [ "buildoutputs" ] } ) ] ;
2013-04-26 08:51:49 -04:00
$ c - > stash - > { buildsdrv } = [ $ c - > model ( 'DB::Builds' ) - > search (
{ "drvpath" = > trim ( $ query ) } ,
{ order_by = > [ "id desc" ] } ) ] ;
2013-02-22 15:45:10 +01:00
}
2008-10-28 10:19:31 +00:00
1 ;