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" : "" ;
2011-04-19 12:00:54 +00:00
$ c - > stash - > { tracker } = $ ENV { "HYDRA_TRACKER" } ;
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
}
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 } ) ] ;
2008-10-28 10:19:31 +00:00
}
2008-11-26 19:48:04 +00:00
sub login :Local {
my ( $ self , $ c ) = @ _ ;
2011-04-01 07:40:06 +00:00
2008-11-26 19:48:04 +00:00
my $ username = $ c - > request - > params - > { username } || "" ;
my $ password = $ c - > request - > params - > { password } || "" ;
2013-02-21 14:38:18 +01:00
if ( $ username eq "" && $ password eq "" && ! defined $ c - > flash - > { referer } ) {
2011-08-16 14:51:19 +00:00
my $ baseurl = $ c - > uri_for ( '/' ) ;
my $ refurl = $ c - > request - > referer ;
$ c - > flash - > { referer } = $ refurl if $ refurl =~ m/^($baseurl)/ ;
2010-07-06 07:27:55 +00:00
}
2008-11-26 19:48:04 +00:00
if ( $ username && $ password ) {
if ( $ c - > authenticate ( { username = > $ username , password = > $ password } ) ) {
2011-08-16 14:51:19 +00:00
$ c - > response - > redirect ( $ c - > flash - > { referer } || $ c - > uri_for ( '/' ) ) ;
$ c - > flash - > { referer } = undef ;
2008-11-26 19:48:04 +00:00
return ;
2008-11-26 23:25:24 +00:00
}
2008-11-26 19:48:04 +00:00
$ c - > stash - > { errorMsg } = "Bad username or password." ;
}
2011-04-01 07:40:06 +00:00
2013-02-21 14:38:18 +01:00
$ c - > keep_flash ( "referer" ) ;
2008-11-26 19:48:04 +00:00
$ c - > stash - > { template } = 'login.tt' ;
}
sub logout :Local {
my ( $ self , $ c ) = @ _ ;
$ c - > logout ;
2011-08-16 14:51:19 +00:00
$ c - > response - > redirect ( $ c - > request - > referer || $ c - > uri_for ( '/' ) ) ;
2008-11-26 19:48:04 +00:00
}
2008-11-26 17:43:45 +00:00
sub queue :Local {
2008-11-26 19:48:04 +00:00
my ( $ self , $ c ) = @ _ ;
2008-11-26 17:43:45 +00:00
$ c - > stash - > { template } = 'queue.tt' ;
$ c - > stash - > { queue } = [ $ c - > model ( 'DB::Builds' ) - > search (
2012-03-07 22:20:15 +01:00
{ finished = > 0 } , { join = > [ 'project' ] , order_by = > [ "priority DESC" , "timestamp" ] , columns = > [ @ buildListColumns ] , '+select' = > [ 'project.enabled' ] , '+as' = > [ 'enabled' ] } ) ] ;
2009-10-26 14:30:42 +00:00
$ c - > stash - > { flashMsg } = $ c - > flash - > { buildMsg } ;
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
sub status :Local {
my ( $ self , $ c ) = @ _ ;
2012-03-12 19:42:59 +01:00
$ c - > stash - > { steps } = [ $ c - > model ( 'DB::BuildSteps' ) - > search (
{ 'me.busy' = > 1 , 'build.finished' = > 0 , 'build.busy' = > 1 } ,
{ join = > [ 'build' ]
, 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 ) = @ _ ;
$ c - > stash - > { machines } = [ $ c - > model ( 'DB::BuildMachines' ) - > search (
{ } ,
{ order_by = > [ "enabled DESC" , "hostname" ]
, '+select' = > [ "(select bs.stoptime from buildsteps as bs where bs.machine = (me.username || '\@' || me.hostname) and not bs.stoptime is null order by bs.stoptime desc limit 1)" ]
, '+as' = > [ 'idle' ]
} ) ] ;
$ 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 ) = @ _ ;
if ( scalar @ { $ c - > error } ) {
$ 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 ;
}
}
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-01-22 14:41:02 +01: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-01-22 14:09:37 +01: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 hashToPath {
my ( $ c , $ hash ) = @ _ ;
die if length ( $ hash ) != 32 ;
2012-07-18 23:14:17 +02:00
my $ path = queryPathFromHashPart ( $ hash ) ;
notFound ( $ c , "Store path with hash ‘ $hash’ does not exist." ) unless $ path ;
return $ path ;
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 ] ;
$ c - > stash - > { storePath } = hashToPath ( $ c , $ hash ) ;
$ c - > stash - > { current_view } = 'NARInfo' ;
2012-07-02 15:18:30 +00:00
}
2012-07-30 20:26:34 +00:00
sub change_password : Path('change-password') :Args(0) {
2010-12-03 09:40:25 +00:00
my ( $ self , $ c ) = @ _ ;
2011-04-01 07:40:06 +00:00
2010-12-03 09:40:25 +00:00
requireLogin ( $ c ) if ! $ c - > user_exists ;
2011-04-01 07:40:06 +00:00
$ c - > stash - > { template } = 'change-password.tt' ;
2010-12-03 09:40:25 +00:00
}
2012-07-02 15:18:30 +00:00
2010-12-03 09:40:25 +00:00
sub change_password_submit : Path('change-password/submit') : Args(0) {
my ( $ self , $ c ) = @ _ ;
2011-04-01 07:40:06 +00:00
2010-12-03 09:40:25 +00:00
requireLogin ( $ c ) if ! $ c - > user_exists ;
2011-04-01 07:40:06 +00:00
my $ password = $ c - > request - > params - > { "password" } ;
2010-12-03 09:40:25 +00:00
my $ password_check = $ c - > request - > params - > { "password_check" } ;
print STDERR "$password \n" ;
print STDERR "$password_check \n" ;
error ( $ c , "Passwords did not match, go back and try again!" ) if $ password ne $ password_check ;
2011-04-01 07:40:06 +00:00
2010-12-03 09:40:25 +00:00
my $ hashed = sha1_hex ( $ password ) ;
$ c - > user - > update ( { password = > $ hashed } ) ;
2011-04-01 07:40:06 +00:00
$ c - > res - > redirect ( "/" ) ;
2010-12-03 09:40:25 +00:00
}
2010-06-22 12:00:19 +00:00
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." )
unless $ query =~ /^[a-zA-Z0-9_\-]+$/ ;
$ 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" ]
, "+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)) enabled_" ]
, "+as" = > [ "enabled" ]
} ) ] ;
2013-02-22 15:45:10 +01:00
}
2008-10-28 10:19:31 +00:00
1 ;