* Move everything up one directory.

This commit is contained in:
Eelco Dolstra
2009-03-05 13:41:57 +00:00
parent 6de278754a
commit 97ed2052ba
84 changed files with 0 additions and 0 deletions

View File

@ -0,0 +1,58 @@
package Hydra::Base::Controller::ListBuilds;
use strict;
use warnings;
use base 'Hydra::Base::Controller::NixChannel';
use Hydra::Helper::Nix;
use Hydra::Helper::CatalystUtils;
sub jobstatus : Chained('get_builds') PathPart Args(0) {
my ($self, $c) = @_;
$c->stash->{template} = 'jobstatus.tt';
$c->stash->{latestBuilds} = getLatestBuilds($c, $c->stash->{allBuilds}, {});
}
sub all : Chained('get_builds') PathPart {
my ($self, $c, $page) = @_;
$c->stash->{template} = 'all.tt';
$page = (defined $page ? int($page) : 1) || 1;
my $resultsPerPage = 50;
my $nrBuilds = scalar($c->stash->{allBuilds}->search({finished => 1}));
$c->stash->{baseUri} = $c->uri_for($self->action_for("all"), $c->req->captures);
$c->stash->{page} = $page;
$c->stash->{resultsPerPage} = $resultsPerPage;
$c->stash->{totalBuilds} = $nrBuilds;
$c->stash->{builds} = [$c->stash->{allBuilds}->search(
{finished => 1}, {order_by => "timestamp DESC", rows => $resultsPerPage, page => $page})];
}
sub nix : Chained('get_builds') PathPart('channel') CaptureArgs(1) {
my ($self, $c, $channelName) = @_;
eval {
if ($channelName eq "latest") {
$c->stash->{channelName} = $c->stash->{channelBaseName} . "-latest";
getChannelData($c, getLatestBuilds($c, $c->stash->{allBuilds}, {buildStatus => 0}));
}
elsif ($channelName eq "all") {
$c->stash->{channelName} = $c->stash->{channelBaseName} . "-all";
getChannelData($c, [$c->stash->{allBuilds}->all]);
}
else {
error($c, "Unknown channel `$channelName'.");
}
};
error($c, $@) if $@;
}
1;

View File

@ -0,0 +1,88 @@
package Hydra::Base::Controller::NixChannel;
use strict;
use warnings;
use base 'Catalyst::Controller';
use Hydra::Helper::Nix;
use Hydra::Helper::CatalystUtils;
sub closure : Chained('nix') PathPart {
my ($self, $c) = @_;
$c->stash->{current_view} = 'Hydra::View::NixClosure';
# !!! quick hack; this is to make HEAD requests return the right
# MIME type. This is set in the view as well, but the view isn't
# called for HEAD requests. There should be a cleaner solution...
$c->response->content_type('application/x-nix-export');
}
sub manifest : Chained('nix') PathPart("MANIFEST") Args(0) {
my ($self, $c) = @_;
$c->stash->{current_view} = 'Hydra::View::NixManifest';
$c->stash->{narBase} = $c->uri_for($self->action_for("nar"), $c->req->captures);
}
sub nar : Chained('nix') PathPart {
my ($self, $c, @rest) = @_;
my $path .= "/" . join("/", @rest);
error($c, "Path " . $path . " is no longer available.") unless isValidPath($path);
# !!! check that $path is in the closure of $c->stash->{storePaths}.
$c->stash->{current_view} = 'Hydra::View::NixNAR';
$c->stash->{storePath} = $path;
}
sub pkg : Chained('nix') PathPart Args(1) {
my ($self, $c, $pkgName) = @_;
my $pkg = $c->stash->{nixPkgs}->{$pkgName};
notFound($c, "Unknown Nix package `$pkgName'.")
unless defined $pkg;
$c->stash->{build} = $pkg->{build};
$c->stash->{manifestUri} = $c->uri_for($self->action_for("manifest"), $c->req->captures);
$c->stash->{current_view} = 'Hydra::View::NixPkg';
$c->response->content_type('application/nix-package');
}
sub nixexprs : Chained('nix') PathPart('nixexprs.tar.bz2') Args(0) {
my ($self, $c) = @_;
$c->stash->{current_view} = 'Hydra::View::NixExprs';
}
sub name {
my ($build) = @_;
return $build->resultInfo->releasename || $build->nixname;
}
sub sortPkgs {
# Sort by name, then timestamp.
return sort
{ lc(name($a->{build})) cmp lc(name($b->{build}))
or $a->{build}->timestamp <=> $b->{build}->timestamp
} @_;
}
sub channel_contents : Chained('nix') PathPart('') Args(0) {
my ($self, $c) = @_;
$c->stash->{template} = 'channel-contents.tt';
$c->stash->{nixPkgs} = [sortPkgs (values %{$c->stash->{nixPkgs}})];
}
1;

View File

@ -0,0 +1,190 @@
package Hydra::Controller::Build;
use strict;
use warnings;
use base 'Hydra::Base::Controller::NixChannel';
use Hydra::Helper::Nix;
use Hydra::Helper::CatalystUtils;
sub build : Chained('/') PathPart CaptureArgs(1) {
my ($self, $c, $id) = @_;
$c->stash->{id} = $id;
$c->stash->{build} = getBuild($c, $id);
notFound($c, "Build with ID $id doesn't exist.")
if !defined $c->stash->{build};
$c->stash->{curProject} = $c->stash->{build}->project;
}
sub view_build : Chained('build') PathPart('') Args(0) {
my ($self, $c) = @_;
my $build = $c->stash->{build};
$c->stash->{template} = 'build.tt';
$c->stash->{curTime} = time;
$c->stash->{available} = isValidPath $build->outpath;
$c->stash->{drvAvailable} = isValidPath $build->drvpath;
$c->stash->{flashMsg} = $c->flash->{afterRestart};
if (!$build->finished && $build->schedulingInfo->busy) {
my $logfile = $build->schedulingInfo->logfile;
$c->stash->{logtext} = `cat $logfile`;
}
}
sub view_nixlog : Chained('build') PathPart('nixlog') Args(1) {
my ($self, $c, $stepnr) = @_;
my $step = $c->stash->{build}->buildsteps->find({stepnr => $stepnr});
notFound($c, "Build doesn't have a build step $stepnr.") if !defined $step;
$c->stash->{template} = 'log.tt';
$c->stash->{step} = $step;
# !!! should be done in the view (as a TT plugin).
$c->stash->{logtext} = loadLog($c, $step->logfile);
}
sub view_log : Chained('build') PathPart('log') Args(0) {
my ($self, $c) = @_;
error($c, "Build didn't produce a log.") if !defined $c->stash->{build}->resultInfo->logfile;
$c->stash->{template} = 'log.tt';
# !!! should be done in the view (as a TT plugin).
$c->stash->{logtext} = loadLog($c, $c->stash->{build}->resultInfo->logfile);
}
sub loadLog {
my ($c, $path) = @_;
notFound($c, "Log file $path no longer exists.") unless -f $path;
# !!! quick hack
my $pipeline = ($path =~ /.bz2$/ ? "cat $path | bzip2 -d" : "cat $path")
. " | nix-log2xml | xsltproc " . $c->path_to("xsl/mark-errors.xsl") . " -"
. " | xsltproc " . $c->path_to("xsl/log2html.xsl") . " - | tail -n +2";
return `$pipeline`;
}
sub download : Chained('build') PathPart('download') {
my ($self, $c, $productnr, $filename, @path) = @_;
my $product = $c->stash->{build}->buildproducts->find({productnr => $productnr});
notFound($c, "Build doesn't have a product $productnr.") if !defined $product;
notFound($c, "Product " . $product->path . " has disappeared.") unless -e $product->path;
# Security paranoia.
foreach my $elem (@path) {
error($c, "Invalid filename $elem.") if $elem !~ /^$pathCompRE$/;
}
my $path = $product->path;
$path .= "/" . join("/", @path) if scalar @path > 0;
# If this is a directory but no "/" is attached, then redirect.
if (-d $path && substr($c->request->uri, -1) ne "/") {
return $c->res->redirect($c->request->uri . "/");
}
$path = "$path/index.html" if -d $path && -e "$path/index.html";
notFound($c, "File $path does not exist.") if !-e $path;
$c->serve_static_file($path);
}
sub runtimedeps : Chained('build') PathPart('runtime-deps') {
my ($self, $c) = @_;
my $build = $c->stash->{build};
notFound($c, "Path " . $build->outpath . " is no longer available.")
unless isValidPath($build->outpath);
$c->stash->{current_view} = 'Hydra::View::NixDepGraph';
$c->stash->{storePaths} = [$build->outpath];
$c->res->content_type('image/png'); # !!!
}
sub buildtimedeps : Chained('build') PathPart('buildtime-deps') {
my ($self, $c) = @_;
my $build = $c->stash->{build};
notFound($c, "Path " . $build->drvpath . " is no longer available.")
unless isValidPath($build->drvpath);
$c->stash->{current_view} = 'Hydra::View::NixDepGraph';
$c->stash->{storePaths} = [$build->drvpath];
$c->res->content_type('image/png'); # !!!
}
sub nix : Chained('build') PathPart('nix') CaptureArgs(0) {
my ($self, $c) = @_;
my $build = $c->stash->{build};
notFound($c, "Build cannot be downloaded as a closure or Nix package.")
if !$build->buildproducts->find({type => "nix-build"});
notFound($c, "Path " . $build->outpath . " is no longer available.")
unless isValidPath($build->outpath);
$c->stash->{storePaths} = [$build->outpath];
my $pkgName = $build->nixname . "-" . $build->system;
$c->stash->{nixPkgs} = {"${pkgName}.nixpkg" => {build => $build, name => $pkgName}};
}
sub restart : Chained('build') PathPart('restart') Args(0) {
my ($self, $c) = @_;
my $build = $c->stash->{build};
requireProjectOwner($c, $build->project);
error($c, "This build cannot be restarted.")
unless $build->finished && $build->resultInfo->buildstatus == 3;
$c->model('DB')->schema->txn_do(sub {
$build->finished(0);
$build->timestamp(time());
$build->update;
$build->resultInfo->delete;
$c->model('DB::BuildSchedulingInfo')->create(
{ id => $build->id
, priority => 0 # don't know the original priority anymore...
, busy => 0
, locker => ""
});
});
$c->flash->{afterRestart} = "Build has been restarted.";
$c->res->redirect($c->uri_for($self->action_for("view_build"), $c->req->captures));
}
1;

View File

@ -0,0 +1,35 @@
package Hydra::Controller::Job;
use strict;
use warnings;
use base 'Hydra::Base::Controller::ListBuilds';
use Hydra::Helper::Nix;
use Hydra::Helper::CatalystUtils;
sub job : Chained('/project/project') PathPart('job') CaptureArgs(1) {
my ($self, $c, $jobName) = @_;
$c->stash->{jobName} = $jobName;
# !!! nothing to do here yet, since we don't have a jobs table.
}
sub index : Chained('job') PathPart('') Args(0) {
my ($self, $c) = @_;
$c->go($self->action_for("all"));
}
# Hydra::Base::Controller::ListBuilds needs this.
sub get_builds : Chained('job') PathPart('') CaptureArgs(0) {
my ($self, $c) = @_;
$c->stash->{allBuilds} =
$c->stash->{curProject}->builds->search({attrName => $c->stash->{jobName}});
$c->stash->{channelBaseName} =
$c->stash->{curProject}->name . "-" . $c->stash->{jobName};
}
1;

View File

@ -0,0 +1,235 @@
package Hydra::Controller::Project;
use strict;
use warnings;
use base 'Hydra::Base::Controller::ListBuilds';
use Hydra::Helper::Nix;
use Hydra::Helper::CatalystUtils;
sub project : Chained('/') PathPart('project') CaptureArgs(1) {
my ($self, $c, $projectName) = @_;
my $project = $c->model('DB::Projects')->find($projectName);
notFound($c, "Project $projectName doesn't exist.") unless defined $project;
$c->stash->{curProject} = $project;
}
sub view : Chained('project') PathPart('') Args(0) {
my ($self, $c) = @_;
$c->stash->{template} = 'project.tt';
getBuildStats($c, scalar $c->stash->{curProject}->builds);
}
sub edit : Chained('project') PathPart Args(0) {
my ($self, $c) = @_;
requireProjectOwner($c, $c->stash->{curProject});
$c->stash->{template} = 'project.tt';
$c->stash->{edit} = 1;
}
sub submit : Chained('project') PathPart Args(0) {
my ($self, $c) = @_;
requireProjectOwner($c, $c->stash->{curProject});
error($c, "Request must be POSTed.") if $c->request->method ne "POST";
$c->model('DB')->schema->txn_do(sub {
updateProject($c, $c->stash->{curProject});
});
$c->res->redirect($c->uri_for($self->action_for("view"), $c->req->captures));
}
sub delete : Chained('project') PathPart Args(0) {
my ($self, $c) = @_;
requireProjectOwner($c, $c->stash->{curProject});
error($c, "Request must be POSTed.") if $c->request->method ne "POST";
$c->model('DB')->schema->txn_do(sub {
$c->stash->{curProject}->delete;
});
$c->res->redirect($c->uri_for("/"));
}
sub create : Path('/create-project') {
my ($self, $c) = @_;
requireAdmin($c);
$c->stash->{template} = 'project.tt';
$c->stash->{create} = 1;
$c->stash->{edit} = 1;
}
sub create_submit : Path('/create-project/submit') {
my ($self, $c) = @_;
requireAdmin($c);
my $projectName = trim $c->request->params->{name};
$c->model('DB')->schema->txn_do(sub {
# Note: $projectName is validated in updateProject,
# which will abort the transaction if the name isn't
# valid. Idem for the owner.
my $project = $c->model('DB::Projects')->create(
{name => $projectName, displayname => "", owner => trim $c->request->params->{owner}});
updateProject($c, $project);
});
$c->res->redirect($c->uri_for($self->action_for("view"), [$projectName]));
}
sub updateProject {
my ($c, $project) = @_;
my $projectName = trim $c->request->params->{name};
error($c, "Invalid project name: " . ($projectName || "(empty)")) unless $projectName =~ /^[[:alpha:]]\w*$/;
my $displayName = trim $c->request->params->{displayname};
error($c, "Invalid display name: $displayName") if $displayName eq "";
$project->name($projectName);
$project->displayname($displayName);
$project->description(trim $c->request->params->{description});
$project->homepage(trim $c->request->params->{homepage});
$project->enabled(trim($c->request->params->{enabled}) eq "1" ? 1 : 0);
if ($c->check_user_roles('admin')) {
my $owner = trim $c->request->params->{owner};
error($c, "Invalid owner: $owner")
unless defined $c->model('DB::Users')->find({username => $owner});
$project->owner($owner);
}
$project->update;
my %jobsetNames;
foreach my $param (keys %{$c->request->params}) {
next unless $param =~ /^jobset-(\w+)-name$/;
my $baseName = $1;
next if $baseName eq "template";
my $jobsetName = trim $c->request->params->{"jobset-$baseName-name"};
error($c, "Invalid jobset name: $jobsetName") unless $jobsetName =~ /^[[:alpha:]]\w*$/;
# The Nix expression path must be relative and can't contain ".." elements.
my $nixExprPath = trim $c->request->params->{"jobset-$baseName-nixexprpath"};
error($c, "Invalid Nix expression path: $nixExprPath") if $nixExprPath !~ /^$relPathRE$/;
my $nixExprInput = trim $c->request->params->{"jobset-$baseName-nixexprinput"};
error($c, "Invalid Nix expression input name: $nixExprInput") unless $nixExprInput =~ /^\w+$/;
$jobsetNames{$jobsetName} = 1;
my $jobset;
my $description = trim $c->request->params->{"jobset-$baseName-description"};
if ($baseName =~ /^\d+$/) { # numeric base name is auto-generated, i.e. a new entry
$jobset = $project->jobsets->create(
{ name => $jobsetName
, description => $description
, nixexprpath => $nixExprPath
, nixexprinput => $nixExprInput
});
} else { # it's an existing jobset
$jobset = ($project->jobsets->search({name => $baseName}))[0];
die unless defined $jobset;
$jobset->name($jobsetName);
$jobset->description($description);
$jobset->nixexprpath($nixExprPath);
$jobset->nixexprinput($nixExprInput);
$jobset->update;
}
my %inputNames;
# Process the inputs of this jobset.
foreach my $param (keys %{$c->request->params}) {
next unless $param =~ /^jobset-$baseName-input-(\w+)-name$/;
my $baseName2 = $1;
next if $baseName2 eq "template";
print STDERR "GOT INPUT: $baseName2\n";
my $inputName = trim $c->request->params->{"jobset-$baseName-input-$baseName2-name"};
error($c, "Invalid input name: $inputName") unless $inputName =~ /^[[:alpha:]]\w*$/;
my $inputType = trim $c->request->params->{"jobset-$baseName-input-$baseName2-type"};
error($c, "Invalid input type: $inputType") unless
$inputType eq "svn" || $inputType eq "cvs" || $inputType eq "tarball" ||
$inputType eq "string" || $inputType eq "path" || $inputType eq "boolean";
$inputNames{$inputName} = 1;
my $input;
if ($baseName2 =~ /^\d+$/) { # numeric base name is auto-generated, i.e. a new entry
$input = $jobset->jobsetinputs->create(
{ name => $inputName
, type => $inputType
});
} else { # it's an existing jobset
$input = ($jobset->jobsetinputs->search({name => $baseName2}))[0];
die unless defined $input;
$input->name($inputName);
$input->type($inputType);
$input->update;
}
# 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->{"jobset-$baseName-input-$baseName2-values"};
$values = [] unless defined $values;
$values = [$values] unless ref($values) eq 'ARRAY';
my $altnr = 0;
foreach my $value (@{$values}) {
print STDERR "VALUE: $value\n";
my $value = trim $value;
error($c, "Invalid Boolean value: $value") if
$inputType eq "boolean" && !($value eq "true" || $value eq "false");
$input->jobsetinputalts->create({altnr => $altnr++, value => $value});
}
}
# Get rid of deleted inputs.
my @inputs = $jobset->jobsetinputs->all;
foreach my $input (@inputs) {
$input->delete unless defined $inputNames{$input->name};
}
}
# Get rid of deleted jobsets, i.e., ones that are no longer submitted in the parameters.
my @jobsets = $project->jobsets->all;
foreach my $jobset (@jobsets) {
$jobset->delete unless defined $jobsetNames{$jobset->name};
}
}
# Hydra::Base::Controller::ListBuilds needs this.
sub get_builds : Chained('project') PathPart('') CaptureArgs(0) {
my ($self, $c) = @_;
$c->stash->{allBuilds} = $c->stash->{curProject}->builds;
$c->stash->{channelBaseName} = $c->stash->{curProject}->name;
}
1;

View File

@ -0,0 +1,251 @@
package Hydra::Controller::Root;
use strict;
use warnings;
use base 'Hydra::Base::Controller::ListBuilds';
use Hydra::Helper::Nix;
use Hydra::Helper::CatalystUtils;
# Put this controller at top-level.
__PACKAGE__->config->{namespace} = '';
sub begin :Private {
my ($self, $c) = @_;
$c->stash->{projects} = [$c->model('DB::Projects')->search({}, {order_by => 'displayname'})];
$c->stash->{curUri} = $c->request->uri;
}
sub index :Path :Args(0) {
my ($self, $c) = @_;
$c->stash->{template} = 'index.tt';
getBuildStats($c, $c->model('DB::Builds'));
}
sub login :Local {
my ($self, $c) = @_;
my $username = $c->request->params->{username} || "";
my $password = $c->request->params->{password} || "";
if ($username && $password) {
if ($c->authenticate({username => $username, password => $password})) {
$c->response->redirect(
defined $c->flash->{afterLogin}
? $c->flash->{afterLogin}
: $c->uri_for('/'));
return;
}
$c->stash->{errorMsg} = "Bad username or password.";
}
$c->stash->{template} = 'login.tt';
}
sub logout :Local {
my ($self, $c) = @_;
$c->logout;
$c->response->redirect($c->uri_for('/'));
}
sub queue :Local {
my ($self, $c) = @_;
$c->stash->{template} = 'queue.tt';
$c->stash->{queue} = [$c->model('DB::Builds')->search(
{finished => 0}, {join => 'schedulingInfo', order_by => ["priority DESC", "timestamp"]})];
}
sub releasesets :Local {
my ($self, $c, $projectName) = @_;
$c->stash->{template} = 'releasesets.tt';
my $project = $c->model('DB::Projects')->find($projectName);
notFound($c, "Project $projectName doesn't exist.") if !defined $project;
$c->stash->{curProject} = $project;
$c->stash->{releaseSets} = [$project->releasesets->all];
}
sub getReleaseSet {
my ($c, $projectName, $releaseSetName) = @_;
my $project = $c->model('DB::Projects')->find($projectName);
die "Project $projectName doesn't exist." if !defined $project;
$c->stash->{curProject} = $project;
(my $releaseSet) = $c->model('DB::ReleaseSets')->find($projectName, $releaseSetName);
die "Release set $releaseSetName doesn't exist." if !defined $releaseSet;
$c->stash->{releaseSet} = $releaseSet;
(my $primaryJob) = $releaseSet->releasesetjobs->search({isprimary => 1});
#die "Release set $releaseSetName doesn't have a primary job." if !defined $primaryJob;
my $jobs = [$releaseSet->releasesetjobs->search({},
{order_by => ["isprimary DESC", "job", "attrs"]})];
$c->stash->{jobs} = $jobs;
return ($project, $releaseSet, $primaryJob, $jobs);
}
sub updateReleaseSet {
my ($c, $releaseSet) = @_;
my $releaseSetName = trim $c->request->params->{name};
die "Invalid release set name: $releaseSetName" unless $releaseSetName =~ /^[[:alpha:]]\w*$/;
$releaseSet->name($releaseSetName);
$releaseSet->description(trim $c->request->params->{description});
$releaseSet->update;
$releaseSet->releasesetjobs->delete_all;
foreach my $param (keys %{$c->request->params}) {
next unless $param =~ /^job-(\d+)-name$/;
my $baseName = $1;
my $name = trim $c->request->params->{"job-$baseName-name"};
my $description = trim $c->request->params->{"job-$baseName-description"};
my $attrs = trim $c->request->params->{"job-$baseName-attrs"};
die "Invalid job name: $name" unless $name =~ /^\w+$/;
$releaseSet->releasesetjobs->create(
{ job => $name
, description => $description
, attrs => $attrs
, isprimary => $c->request->params->{"primary"} eq $baseName ? 1 : 0
});
}
die "There must be one primary job." if $releaseSet->releasesetjobs->search({isprimary => 1})->count != 1;
}
sub releases :Local {
my ($self, $c, $projectName, $releaseSetName, $subcommand) = @_;
my ($project, $releaseSet, $primaryJob, $jobs) = getReleaseSet($c, $projectName, $releaseSetName);
if (defined $subcommand && $subcommand ne "") {
requireProjectOwner($c, $project);
if ($subcommand eq "edit") {
$c->stash->{template} = 'edit-releaseset.tt';
return;
}
elsif ($subcommand eq "submit") {
$c->model('DB')->schema->txn_do(sub {
updateReleaseSet($c, $releaseSet);
});
return $c->res->redirect($c->uri_for("/releases", $projectName, $releaseSet->name));
}
elsif ($subcommand eq "delete") {
$c->model('DB')->schema->txn_do(sub {
$releaseSet->delete;
});
return $c->res->redirect($c->uri_for("/releasesets", $projectName));
}
else { error($c, "Unknown subcommand."); }
}
$c->stash->{template} = 'releases.tt';
my @releases = ();
push @releases, getRelease($_, $jobs) foreach getPrimaryBuildsForReleaseSet($project, $primaryJob);
$c->stash->{releases} = [@releases];
}
sub create_releaseset :Local {
my ($self, $c, $projectName, $subcommand) = @_;
my $project = $c->model('DB::Projects')->find($projectName);
die "Project $projectName doesn't exist." if !defined $project;
$c->stash->{curProject} = $project;
requireProjectOwner($c, $project);
if (defined $subcommand && $subcommand eq "submit") {
my $releaseSetName = $c->request->params->{name};
$c->model('DB')->schema->txn_do(sub {
# Note: $releaseSetName is validated in updateProject,
# which will abort the transaction if the name isn't
# valid.
my $releaseSet = $project->releasesets->create({name => $releaseSetName});
updateReleaseSet($c, $releaseSet);
return $c->res->redirect($c->uri_for("/releases", $projectName, $releaseSet->name));
});
}
$c->stash->{template} = 'edit-releaseset.tt';
$c->stash->{create} = 1;
}
sub release :Local {
my ($self, $c, $projectName, $releaseSetName, $releaseId) = @_;
$c->stash->{template} = 'release.tt';
my ($project, $releaseSet, $primaryJob, $jobs) = getReleaseSet($c, $projectName, $releaseSetName);
if ($releaseId eq "latest") {
# Redirect to the latest successful release.
my $latest = getLatestSuccessfulRelease($project, $primaryJob, $jobs);
error($c, "This release set has no successful releases yet.") if !defined $latest;
return $c->res->redirect($c->uri_for("/release", $projectName, $releaseSetName, $latest->id));
}
# Note: we don't actually check whether $releaseId is a primary
# build, but who cares?
my $primaryBuild = $project->builds->find($releaseId,
{ join => 'resultInfo', '+select' => ["resultInfo.releasename"], '+as' => ["releasename"] });
error($c, "Release $releaseId doesn't exist.") if !defined $primaryBuild;
$c->stash->{release} = getRelease($primaryBuild, $jobs);
}
# Hydra::Base::Controller::ListBuilds needs this.
sub get_builds : Chained('/') PathPart('') CaptureArgs(0) {
my ($self, $c) = @_;
$c->stash->{allBuilds} = $c->model('DB::Builds');
$c->stash->{channelBaseName} = "everything";
}
sub default :Path {
my ($self, $c) = @_;
notFound($c, "Page not found.");
}
sub end : ActionClass('RenderView') {
my ($self, $c) = @_;
if (scalar @{$c->error}) {
$c->stash->{template} = 'error.tt';
$c->stash->{errors} = $c->error;
if ($c->response->status >= 300) {
$c->stash->{httpStatus} =
$c->response->status . " " . HTTP::Status::status_message($c->response->status);
}
$c->clear_errors;
}
}
1;

View File

@ -0,0 +1,141 @@
package Hydra::Helper::CatalystUtils;
use strict;
use Exporter;
use Readonly;
use Hydra::Helper::Nix;
our @ISA = qw(Exporter);
our @EXPORT = qw(
getBuild getBuildStats getLatestBuilds getChannelData
error notFound
requireLogin requireProjectOwner requireAdmin
trim
$pathCompRE $relPathRE
);
sub getBuild {
my ($c, $id) = @_;
my $build = $c->model('DB::Builds')->find($id);
return $build;
}
sub getBuildStats {
my ($c, $builds) = @_;
$c->stash->{finishedBuilds} = $builds->search({finished => 1}) || 0;
$c->stash->{succeededBuilds} = $builds->search(
{finished => 1, buildStatus => 0},
{join => 'resultInfo'}) || 0;
$c->stash->{scheduledBuilds} = $builds->search({finished => 0}) || 0;
$c->stash->{busyBuilds} = $builds->search(
{finished => 0, busy => 1},
{join => 'schedulingInfo'}) || 0;
$c->stash->{totalBuildTime} = $builds->search({},
{join => 'resultInfo', select => {sum => 'stoptime - starttime'}, as => ['sum']})
->first->get_column('sum') || 0;
}
# Return the latest build for each job.
sub getLatestBuilds {
my ($c, $builds, $extraAttrs) = @_;
my @res = ();
foreach my $job ($builds->search({},
{group_by => ['project', 'attrname', 'system']}))
{
my $attrs =
{ project => $job->get_column('project')
, attrname => $job->attrname
, system => $job->system
, finished => 1
};
my ($build) = $builds->search({ %$attrs, %$extraAttrs },
{ join => 'resultInfo', order_by => 'timestamp DESC', rows => 1 } );
push @res, $build if defined $build;
}
return [@res];
}
sub getChannelData {
my ($c, $builds) = @_;
my @storePaths = ();
foreach my $build (@{$builds}) {
# !!! better do this in getLatestBuilds with a join.
next unless $build->buildproducts->find({type => "nix-build"});
next unless isValidPath($build->outpath);
push @storePaths, $build->outpath;
my $pkgName = $build->nixname . "-" . $build->system . "-" . $build->id;
$c->stash->{nixPkgs}->{"${pkgName}.nixpkg"} = {build => $build, name => $pkgName};
};
$c->stash->{storePaths} = [@storePaths];
}
sub error {
my ($c, $msg) = @_;
$c->error($msg);
$c->detach; # doesn't return
}
sub notFound {
my ($c, $msg) = @_;
$c->response->status(404);
error($c, $msg);
}
sub requireLogin {
my ($c) = @_;
$c->flash->{afterLogin} = $c->request->uri;
$c->response->redirect($c->uri_for('/login'));
$c->detach; # doesn't return
}
sub requireProjectOwner {
my ($c, $project) = @_;
requireLogin($c) if !$c->user_exists;
error($c, "Only the project owner or administrators can perform this operation.")
unless $c->check_user_roles('admin') || $c->user->username eq $project->owner->username;
}
sub requireAdmin {
my ($c) = @_;
requireLogin($c) if !$c->user_exists;
error($c, "Only administrators can perform this operation.")
unless $c->check_user_roles('admin');
}
sub trim {
my $s = shift;
$s =~ s/^\s+|\s+$//g;
return $s;
}
# Security checking of filenames.
Readonly::Scalar our $pathCompRE => "(?:[A-Za-z0-9-\+][A-Za-z0-9-\+\._]*)";
Readonly::Scalar our $relPathRE => "(?:$pathCompRE(?:\/$pathCompRE)*)";
1;

199
src/lib/Hydra/Helper/Nix.pm Normal file
View File

@ -0,0 +1,199 @@
package Hydra::Helper::Nix;
use strict;
use Exporter;
use File::Path;
use File::Basename;
our @ISA = qw(Exporter);
our @EXPORT = qw(
isValidPath queryPathInfo
getHydraPath getHydraDBPath openHydraDB
registerRoot getGCRootsDir
getPrimaryBuildsForReleaseSet getRelease getLatestSuccessfulRelease );
sub isValidPath {
my $path = shift;
#$SIG{CHLD} = 'DEFAULT'; # !!! work around system() failing if SIGCHLD is ignored
#return system("nix-store --check-validity $path 2> /dev/null") == 0;
# This is faster than calling nix-store, but it breaks abstraction...
return -e ("/nix/var/nix/db/info/" . basename $path);
}
sub queryPathInfo {
my $path = shift;
# !!! like above, this breaks abstraction. What we really need is
# Perl bindings for libstore :-)
open FH, "</nix/var/nix/db/info/" . basename $path
or die "cannot open info file for $path";
my $hash;
my $deriver;
my @refs = ();
while (<FH>) {
if (/^Hash: (\S+)$/) {
$hash = $1;
}
elsif (/^Deriver: (\S+)$/) {
$deriver = $1;
}
elsif (/^References: (.*)$/) {
@refs = split / /, $1;
}
}
close FH;
die unless defined $hash;
return ($hash, $deriver, \@refs);
}
sub getHydraPath {
my $dir = $ENV{"HYDRA_DATA"};
die "The HYDRA_DATA environment variable is not set!\n" unless defined $dir;
die "The HYDRA_DATA directory does not exist!\n" unless -d $dir;
return $dir;
}
sub getHydraDBPath {
my $path = getHydraPath . '/hydra.sqlite';
die "The Hydra database ($path) not exist!\n" unless -f $path;
return "dbi:SQLite:$path";
}
sub openHydraDB {
my $db = Hydra::Schema->connect(getHydraDBPath, "", "", {});
$db->storage->dbh->do("PRAGMA synchronous = OFF;");
return $db;
}
sub getGCRootsDir {
die unless defined $ENV{LOGNAME};
return "/nix/var/nix/gcroots/per-user/$ENV{LOGNAME}/hydra-roots";
}
sub registerRoot {
my ($path) = @_;
my $gcRootsDir = getGCRootsDir;
mkpath($gcRootsDir) if !-e $gcRootsDir;
my $link = "$gcRootsDir/" . basename $path;
if (!-l $link) {
symlink($path, $link)
or die "cannot create symlink in $gcRootsDir to $path";
}
}
sub attrsToSQL {
my ($attrs, $id) = @_;
my @attrs = split / /, $attrs;
my $query = "1 = 1";
foreach my $attr (@attrs) {
$attr =~ /^([\w-]+)=([\w-]*)$/ or die "invalid attribute in release set: $attr";
my $name = $1;
my $value = $2;
# !!! Yes, this is horribly injection-prone... (though
# name/value are filtered above). Should use SQL::Abstract,
# but it can't deal with subqueries. At least we should use
# placeholders.
$query .= " and (select count(*) from buildinputs where build = $id and name = '$name' and value = '$value') = 1";
}
return $query;
}
sub getPrimaryBuildsForReleaseSet {
my ($project, $primaryJob) = @_;
my @primaryBuilds = $project->builds->search(
{ attrname => $primaryJob->job, finished => 1 },
{ join => 'resultInfo', order_by => "timestamp DESC"
, '+select' => ["resultInfo.releasename"], '+as' => ["releasename"]
, where => \ attrsToSQL($primaryJob->attrs, "me.id")
});
return @primaryBuilds;
}
sub getRelease {
my ($primaryBuild, $jobs) = @_;
my @jobs = ();
my $status = 0; # = okay
# The timestamp of the release is the highest timestamp of all
# constitutent builds.
my $timestamp = 0;
foreach my $job (@{$jobs}) {
my $thisBuild;
if ($job->isprimary) {
$thisBuild = $primaryBuild;
} else {
# Find a build of this job that had the primary build
# as input. If there are multiple, prefer successful
# ones, and then oldest. !!! order_by buildstatus is hacky
($thisBuild) = $primaryBuild->dependentBuilds->search(
{ attrname => $job->job, finished => 1 },
{ join => 'resultInfo', rows => 1
, order_by => ["buildstatus", "timestamp"]
, where => \ attrsToSQL($job->attrs, "build.id")
});
}
if ($job->mayfail != 1) {
if (!defined $thisBuild) {
$status = 2 if $status == 0; # = unfinished
} elsif ($thisBuild->resultInfo->buildstatus != 0) {
$status = 1; # = failed
}
}
$timestamp = $thisBuild->timestamp
if defined $thisBuild && $thisBuild->timestamp > $timestamp;
push @jobs, { build => $thisBuild, job => $job };
}
return
{ id => $primaryBuild->id
, releasename => $primaryBuild->get_column('releasename')
, jobs => [@jobs]
, status => $status
, timestamp => $timestamp
};
}
sub getLatestSuccessfulRelease {
my ($project, $primaryJob, $jobs) = @_;
my $latest;
foreach my $build (getPrimaryBuildsForReleaseSet($project, $primaryJob)) {
return $build if getRelease($build, $jobs)->{status} == 0;
}
return undef;
}
1;

34
src/lib/Hydra/Model/DB.pm Normal file
View File

@ -0,0 +1,34 @@
package Hydra::Model::DB;
use strict;
use base 'Catalyst::Model::DBIC::Schema';
use Hydra::Helper::Nix;
__PACKAGE__->config(
schema_class => 'Hydra::Schema',
connect_info => [getHydraDBPath],
);
=head1 NAME
Hydra::Model::DB - Catalyst DBIC Schema Model
=head1 SYNOPSIS
See L<Hydra>
=head1 DESCRIPTION
L<Catalyst::Model::DBIC::Schema> Model using schema L<Hydra::Schema>
=head1 AUTHOR
Eelco Dolstra
=head1 LICENSE
This library is free software, you can redistribute it and/or modify
it under the same terms as Perl itself.
=cut
1;

16
src/lib/Hydra/Schema.pm Normal file
View File

@ -0,0 +1,16 @@
package Hydra::Schema;
use strict;
use warnings;
use base 'DBIx::Class::Schema';
__PACKAGE__->load_classes;
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2009-03-04 14:50:30
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:QcB8T/bY2/Pw34uuYXt2Cw
# You can replace this text with custom content, and it will be preserved on regeneration
1;

View File

@ -0,0 +1,44 @@
package Hydra::Schema::BuildInputs;
use strict;
use warnings;
use base 'DBIx::Class';
__PACKAGE__->load_components("Core");
__PACKAGE__->table("BuildInputs");
__PACKAGE__->add_columns(
"id",
{ data_type => "integer", is_nullable => 0, size => undef },
"build",
{ data_type => "integer", is_nullable => 0, size => undef },
"name",
{ data_type => "text", is_nullable => 0, size => undef },
"type",
{ data_type => "text", is_nullable => 0, size => undef },
"uri",
{ data_type => "text", is_nullable => 0, size => undef },
"revision",
{ data_type => "integer", is_nullable => 0, size => undef },
"tag",
{ data_type => "text", is_nullable => 0, size => undef },
"value",
{ data_type => "text", is_nullable => 0, size => undef },
"dependency",
{ data_type => "integer", is_nullable => 0, size => undef },
"path",
{ data_type => "text", is_nullable => 0, size => undef },
"sha256hash",
{ data_type => "text", is_nullable => 0, size => undef },
);
__PACKAGE__->set_primary_key("id");
__PACKAGE__->belongs_to("build", "Hydra::Schema::Builds", { id => "build" });
__PACKAGE__->belongs_to("dependency", "Hydra::Schema::Builds", { id => "dependency" });
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2009-03-04 14:50:30
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:7Th7GxvR7m/DdodQqlmJXQ
# You can replace this text with custom content, and it will be preserved on regeneration
1;

View File

@ -0,0 +1,41 @@
package Hydra::Schema::BuildProducts;
use strict;
use warnings;
use base 'DBIx::Class';
__PACKAGE__->load_components("Core");
__PACKAGE__->table("BuildProducts");
__PACKAGE__->add_columns(
"build",
{ data_type => "integer", is_nullable => 0, size => undef },
"productnr",
{ data_type => "integer", is_nullable => 0, size => undef },
"type",
{ data_type => "text", is_nullable => 0, size => undef },
"subtype",
{ data_type => "text", is_nullable => 0, size => undef },
"filesize",
{ data_type => "integer", is_nullable => 0, size => undef },
"sha1hash",
{ data_type => "text", is_nullable => 0, size => undef },
"sha256hash",
{ data_type => "text", is_nullable => 0, size => undef },
"path",
{ data_type => "text", is_nullable => 0, size => undef },
"name",
{ data_type => "text", is_nullable => 0, size => undef },
"description",
{ data_type => "text", is_nullable => 0, size => undef },
);
__PACKAGE__->set_primary_key("build", "productnr");
__PACKAGE__->belongs_to("build", "Hydra::Schema::Builds", { id => "build" });
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2009-03-04 14:50:30
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:FFVpdoV0vBLhF9yyKJSoTA
# You can replace this text with custom content, and it will be preserved on regeneration
1;

View File

@ -0,0 +1,39 @@
package Hydra::Schema::BuildResultInfo;
use strict;
use warnings;
use base 'DBIx::Class';
__PACKAGE__->load_components("Core");
__PACKAGE__->table("BuildResultInfo");
__PACKAGE__->add_columns(
"id",
{ data_type => "integer", is_nullable => 0, size => undef },
"iscachedbuild",
{ data_type => "integer", is_nullable => 0, size => undef },
"buildstatus",
{ data_type => "integer", is_nullable => 0, size => undef },
"errormsg",
{ data_type => "text", is_nullable => 0, size => undef },
"starttime",
{ data_type => "integer", is_nullable => 0, size => undef },
"stoptime",
{ data_type => "integer", is_nullable => 0, size => undef },
"logfile",
{ data_type => "text", is_nullable => 0, size => undef },
"releasename",
{ data_type => "text", is_nullable => 0, size => undef },
"keep",
{ data_type => "integer", is_nullable => 0, size => undef },
);
__PACKAGE__->set_primary_key("id");
__PACKAGE__->belongs_to("id", "Hydra::Schema::Builds", { id => "id" });
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2009-03-04 14:50:30
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:CkjyZptB79J32VhDbXhKEg
# You can replace this text with custom content, and it will be preserved on regeneration
1;

View File

@ -0,0 +1,35 @@
package Hydra::Schema::BuildSchedulingInfo;
use strict;
use warnings;
use base 'DBIx::Class';
__PACKAGE__->load_components("Core");
__PACKAGE__->table("BuildSchedulingInfo");
__PACKAGE__->add_columns(
"id",
{ data_type => "integer", is_nullable => 0, size => undef },
"priority",
{ data_type => "integer", is_nullable => 0, size => undef },
"busy",
{ data_type => "integer", is_nullable => 0, size => undef },
"locker",
{ data_type => "text", is_nullable => 0, size => undef },
"logfile",
{ data_type => "text", is_nullable => 0, size => undef },
"disabled",
{ data_type => "integer", is_nullable => 0, size => undef },
"starttime",
{ data_type => "integer", is_nullable => 0, size => undef },
);
__PACKAGE__->set_primary_key("id");
__PACKAGE__->belongs_to("id", "Hydra::Schema::Builds", { id => "id" });
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2009-03-04 14:50:30
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:HeodRHEOs/do8RKwDJhaXg
# You can replace this text with custom content, and it will be preserved on regeneration
1;

View File

@ -0,0 +1,43 @@
package Hydra::Schema::BuildSteps;
use strict;
use warnings;
use base 'DBIx::Class';
__PACKAGE__->load_components("Core");
__PACKAGE__->table("BuildSteps");
__PACKAGE__->add_columns(
"id",
{ data_type => "integer", is_nullable => 0, size => undef },
"stepnr",
{ data_type => "integer", is_nullable => 0, size => undef },
"type",
{ data_type => "integer", is_nullable => 0, size => undef },
"drvpath",
{ data_type => "text", is_nullable => 0, size => undef },
"outpath",
{ data_type => "text", is_nullable => 0, size => undef },
"logfile",
{ data_type => "text", is_nullable => 0, size => undef },
"busy",
{ data_type => "integer", is_nullable => 0, size => undef },
"status",
{ data_type => "integer", is_nullable => 0, size => undef },
"errormsg",
{ data_type => "text", is_nullable => 0, size => undef },
"starttime",
{ data_type => "integer", is_nullable => 0, size => undef },
"stoptime",
{ data_type => "integer", is_nullable => 0, size => undef },
);
__PACKAGE__->set_primary_key("id", "stepnr");
__PACKAGE__->belongs_to("id", "Hydra::Schema::Builds", { id => "id" });
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2009-03-04 14:50:30
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:kKpIuRMrqdh7m4M5XPIEgg
# You can replace this text with custom content, and it will be preserved on regeneration
1;

View File

@ -0,0 +1,100 @@
package Hydra::Schema::Builds;
use strict;
use warnings;
use base 'DBIx::Class';
__PACKAGE__->load_components("Core");
__PACKAGE__->table("Builds");
__PACKAGE__->add_columns(
"id",
{ data_type => "integer", is_nullable => 0, size => undef },
"finished",
{ data_type => "integer", is_nullable => 0, size => undef },
"timestamp",
{ data_type => "integer", is_nullable => 0, size => undef },
"project",
{ data_type => "text", is_nullable => 0, size => undef },
"jobset",
{ data_type => "text", is_nullable => 0, size => undef },
"attrname",
{ data_type => "text", is_nullable => 0, size => undef },
"nixname",
{ data_type => "text", is_nullable => 0, size => undef },
"description",
{ data_type => "text", is_nullable => 0, size => undef },
"drvpath",
{ data_type => "text", is_nullable => 0, size => undef },
"outpath",
{ data_type => "text", is_nullable => 0, size => undef },
"system",
{ data_type => "text", is_nullable => 0, size => undef },
"longdescription",
{ data_type => "text", is_nullable => 0, size => undef },
"license",
{ data_type => "text", is_nullable => 0, size => undef },
"homepage",
{ data_type => "text", is_nullable => 0, size => undef },
);
__PACKAGE__->set_primary_key("id");
__PACKAGE__->belongs_to("project", "Hydra::Schema::Projects", { name => "project" });
__PACKAGE__->belongs_to(
"jobset",
"Hydra::Schema::Jobsets",
{ name => "jobset", project => "project" },
);
__PACKAGE__->has_many(
"buildschedulinginfoes",
"Hydra::Schema::BuildSchedulingInfo",
{ "foreign.id" => "self.id" },
);
__PACKAGE__->has_many(
"buildresultinfoes",
"Hydra::Schema::BuildResultInfo",
{ "foreign.id" => "self.id" },
);
__PACKAGE__->has_many(
"buildsteps",
"Hydra::Schema::BuildSteps",
{ "foreign.id" => "self.id" },
);
__PACKAGE__->has_many(
"buildinputs_builds",
"Hydra::Schema::BuildInputs",
{ "foreign.build" => "self.id" },
);
__PACKAGE__->has_many(
"buildinputs_dependencies",
"Hydra::Schema::BuildInputs",
{ "foreign.dependency" => "self.id" },
);
__PACKAGE__->has_many(
"buildproducts",
"Hydra::Schema::BuildProducts",
{ "foreign.build" => "self.id" },
);
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2009-03-04 14:50:30
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:fGOOscNFEgDZpeVpA6HH0w
__PACKAGE__->has_many(dependents => 'Hydra::Schema::BuildInputs', 'dependency');
__PACKAGE__->many_to_many(dependentBuilds => 'dependents', 'build');
__PACKAGE__->has_many(inputs => 'Hydra::Schema::BuildInputs', 'build');
__PACKAGE__->belongs_to(
"schedulingInfo",
"Hydra::Schema::BuildSchedulingInfo",
{ id => "id" },
);
__PACKAGE__->belongs_to(
"resultInfo",
"Hydra::Schema::BuildResultInfo",
{ id => "id" },
);
1;

View File

@ -0,0 +1,30 @@
package Hydra::Schema::CachedPathInputs;
use strict;
use warnings;
use base 'DBIx::Class';
__PACKAGE__->load_components("Core");
__PACKAGE__->table("CachedPathInputs");
__PACKAGE__->add_columns(
"srcpath",
{ data_type => "text", is_nullable => 0, size => undef },
"timestamp",
{ data_type => "integer", is_nullable => 0, size => undef },
"lastseen",
{ data_type => "integer", is_nullable => 0, size => undef },
"sha256hash",
{ data_type => "text", is_nullable => 0, size => undef },
"storepath",
{ data_type => "text", is_nullable => 0, size => undef },
);
__PACKAGE__->set_primary_key("srcpath", "sha256hash");
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2009-03-04 14:50:30
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:0wY3JTSelPQSTbxpNQDJjg
# You can replace this text with custom content, and it will be preserved on regeneration
1;

View File

@ -0,0 +1,28 @@
package Hydra::Schema::CachedSubversionInputs;
use strict;
use warnings;
use base 'DBIx::Class';
__PACKAGE__->load_components("Core");
__PACKAGE__->table("CachedSubversionInputs");
__PACKAGE__->add_columns(
"uri",
{ data_type => "text", is_nullable => 0, size => undef },
"revision",
{ data_type => "integer", is_nullable => 0, size => undef },
"sha256hash",
{ data_type => "text", is_nullable => 0, size => undef },
"storepath",
{ data_type => "text", is_nullable => 0, size => undef },
);
__PACKAGE__->set_primary_key("uri", "revision");
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2009-03-04 14:50:30
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:eZPs3SB3XZW5BNQOQFrFBw
# You can replace this text with custom content, and it will be preserved on regeneration
1;

View File

@ -0,0 +1,39 @@
package Hydra::Schema::JobsetInputAlts;
use strict;
use warnings;
use base 'DBIx::Class';
__PACKAGE__->load_components("Core");
__PACKAGE__->table("JobsetInputAlts");
__PACKAGE__->add_columns(
"project",
{ data_type => "text", is_nullable => 0, size => undef },
"jobset",
{ data_type => "text", is_nullable => 0, size => undef },
"input",
{ data_type => "text", is_nullable => 0, size => undef },
"altnr",
{ data_type => "integer", is_nullable => 0, size => undef },
"value",
{ data_type => "text", is_nullable => 0, size => undef },
"revision",
{ data_type => "integer", is_nullable => 0, size => undef },
"tag",
{ data_type => "text", is_nullable => 0, size => undef },
);
__PACKAGE__->set_primary_key("project", "jobset", "input", "altnr");
__PACKAGE__->belongs_to(
"jobsetinput",
"Hydra::Schema::JobsetInputs",
{ jobset => "jobset", name => "input", project => "project" },
);
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2009-03-04 14:50:30
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:atlyxsSKg41KbDkbCfuvHQ
# You can replace this text with custom content, and it will be preserved on regeneration
1;

View File

@ -0,0 +1,51 @@
package Hydra::Schema::JobsetInputs;
use strict;
use warnings;
use base 'DBIx::Class';
__PACKAGE__->load_components("Core");
__PACKAGE__->table("JobsetInputs");
__PACKAGE__->add_columns(
"project",
{ data_type => "text", is_nullable => 0, size => undef },
"jobset",
{ data_type => "text", is_nullable => 0, size => undef },
"name",
{ data_type => "text", is_nullable => 0, size => undef },
"type",
{ data_type => "text", is_nullable => 0, size => undef },
);
__PACKAGE__->set_primary_key("project", "jobset", "name");
__PACKAGE__->has_many(
"jobsets",
"Hydra::Schema::Jobsets",
{
"foreign.name" => "self.job",
"foreign.nixexprinput" => "self.name",
"foreign.project" => "self.project",
},
);
__PACKAGE__->belongs_to(
"jobset",
"Hydra::Schema::Jobsets",
{ name => "jobset", project => "project" },
);
__PACKAGE__->has_many(
"jobsetinputalts",
"Hydra::Schema::JobsetInputAlts",
{
"foreign.input" => "self.name",
"foreign.jobset" => "self.jobset",
"foreign.project" => "self.project",
},
);
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2009-03-04 14:50:30
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:85ro4sVmhc3HwAjgoA6p6w
# You can replace this text with custom content, and it will be preserved on regeneration
1;

View File

@ -0,0 +1,58 @@
package Hydra::Schema::Jobsets;
use strict;
use warnings;
use base 'DBIx::Class';
__PACKAGE__->load_components("Core");
__PACKAGE__->table("Jobsets");
__PACKAGE__->add_columns(
"name",
{ data_type => "text", is_nullable => 0, size => undef },
"project",
{ data_type => "text", is_nullable => 0, size => undef },
"description",
{ data_type => "text", is_nullable => 0, size => undef },
"nixexprinput",
{ data_type => "text", is_nullable => 0, size => undef },
"nixexprpath",
{ data_type => "text", is_nullable => 0, size => undef },
"errormsg",
{ data_type => "text", is_nullable => 0, size => undef },
"errortime",
{ data_type => "integer", is_nullable => 0, size => undef },
"lastcheckedtime",
{ data_type => "integer", is_nullable => 0, size => undef },
);
__PACKAGE__->set_primary_key("project", "name");
__PACKAGE__->has_many(
"builds",
"Hydra::Schema::Builds",
{
"foreign.jobset" => "self.name",
"foreign.project" => "self.project",
},
);
__PACKAGE__->belongs_to("project", "Hydra::Schema::Projects", { name => "project" });
__PACKAGE__->belongs_to(
"jobsetinput",
"Hydra::Schema::JobsetInputs",
{ job => "name", name => "nixexprinput", project => "project" },
);
__PACKAGE__->has_many(
"jobsetinputs",
"Hydra::Schema::JobsetInputs",
{
"foreign.jobset" => "self.name",
"foreign.project" => "self.project",
},
);
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2009-03-04 14:50:30
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:c5PqrzN43jEGGrzKqI6WWQ
# You can replace this text with custom content, and it will be preserved on regeneration
1;

View File

@ -0,0 +1,53 @@
package Hydra::Schema::Projects;
use strict;
use warnings;
use base 'DBIx::Class';
__PACKAGE__->load_components("Core");
__PACKAGE__->table("Projects");
__PACKAGE__->add_columns(
"name",
{ data_type => "text", is_nullable => 0, size => undef },
"displayname",
{ data_type => "text", is_nullable => 0, size => undef },
"description",
{ data_type => "text", is_nullable => 0, size => undef },
"enabled",
{ data_type => "integer", is_nullable => 0, size => undef },
"owner",
{ data_type => "text", is_nullable => 0, size => undef },
"homepage",
{ data_type => "text", is_nullable => 0, size => undef },
);
__PACKAGE__->set_primary_key("name");
__PACKAGE__->has_many(
"builds",
"Hydra::Schema::Builds",
{ "foreign.project" => "self.name" },
);
__PACKAGE__->belongs_to("owner", "Hydra::Schema::Users", { username => "owner" });
__PACKAGE__->has_many(
"jobsets",
"Hydra::Schema::Jobsets",
{ "foreign.project" => "self.name" },
);
__PACKAGE__->has_many(
"releasesets",
"Hydra::Schema::ReleaseSets",
{ "foreign.project" => "self.name" },
);
__PACKAGE__->has_many(
"releasesetjobs",
"Hydra::Schema::ReleaseSetJobs",
{ "foreign.project" => "self.name" },
);
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2009-03-04 14:50:30
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:7iZYMDM+wn+Neud0Fm1ZMA
# You can replace this text with custom content, and it will be preserved on regeneration
1;

View File

@ -0,0 +1,40 @@
package Hydra::Schema::ReleaseSetJobs;
use strict;
use warnings;
use base 'DBIx::Class';
__PACKAGE__->load_components("Core");
__PACKAGE__->table("ReleaseSetJobs");
__PACKAGE__->add_columns(
"project",
{ data_type => "text", is_nullable => 0, size => undef },
"release",
{ data_type => "text", is_nullable => 0, size => undef },
"job",
{ data_type => "text", is_nullable => 0, size => undef },
"attrs",
{ data_type => "text", is_nullable => 0, size => undef },
"isprimary",
{ data_type => "integer", is_nullable => 0, size => undef },
"mayfail",
{ data_type => "integer", is_nullable => 0, size => undef },
"description",
{ data_type => "text", is_nullable => 0, size => undef },
);
__PACKAGE__->set_primary_key("project", "release", "job", "attrs");
__PACKAGE__->belongs_to("project", "Hydra::Schema::Projects", { name => "project" });
__PACKAGE__->belongs_to(
"releaseset",
"Hydra::Schema::ReleaseSets",
{ name => "release", project => "project" },
);
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2009-03-04 14:50:30
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:Rs3CRPpzFi30sAeHVe1yQA
# You can replace this text with custom content, and it will be preserved on regeneration
1;

View File

@ -0,0 +1,37 @@
package Hydra::Schema::ReleaseSets;
use strict;
use warnings;
use base 'DBIx::Class';
__PACKAGE__->load_components("Core");
__PACKAGE__->table("ReleaseSets");
__PACKAGE__->add_columns(
"project",
{ data_type => "text", is_nullable => 0, size => undef },
"name",
{ data_type => "text", is_nullable => 0, size => undef },
"description",
{ data_type => "text", is_nullable => 0, size => undef },
"keep",
{ data_type => "integer", is_nullable => 0, size => undef },
);
__PACKAGE__->set_primary_key("project", "name");
__PACKAGE__->belongs_to("project", "Hydra::Schema::Projects", { name => "project" });
__PACKAGE__->has_many(
"releasesetjobs",
"Hydra::Schema::ReleaseSetJobs",
{
"foreign.project" => "self.project",
"foreign.release" => "self.name",
},
);
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2009-03-04 14:50:30
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:PA+dfXHaBsSx9kE1mEZZ9w
# You can replace this text with custom content, and it will be preserved on regeneration
1;

View File

@ -0,0 +1,24 @@
package Hydra::Schema::SystemTypes;
use strict;
use warnings;
use base 'DBIx::Class';
__PACKAGE__->load_components("Core");
__PACKAGE__->table("SystemTypes");
__PACKAGE__->add_columns(
"system",
{ data_type => "text", is_nullable => 0, size => undef },
"maxconcurrent",
{ data_type => "integer", is_nullable => 0, size => undef },
);
__PACKAGE__->set_primary_key("system");
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2009-03-04 14:50:30
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:olHboRdtxD6E7Ukr4aCLCA
# You can replace this text with custom content, and it will be preserved on regeneration
1;

View File

@ -0,0 +1,25 @@
package Hydra::Schema::UserRoles;
use strict;
use warnings;
use base 'DBIx::Class';
__PACKAGE__->load_components("Core");
__PACKAGE__->table("UserRoles");
__PACKAGE__->add_columns(
"username",
{ data_type => "text", is_nullable => 0, size => undef },
"role",
{ data_type => "text", is_nullable => 0, size => undef },
);
__PACKAGE__->set_primary_key("username", "role");
__PACKAGE__->belongs_to("username", "Hydra::Schema::Users", { username => "username" });
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2009-03-04 14:50:30
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:Q7Nd3wv7Y3184GhkE/pdFA
# You can replace this text with custom content, and it will be preserved on regeneration
1;

View File

@ -0,0 +1,38 @@
package Hydra::Schema::Users;
use strict;
use warnings;
use base 'DBIx::Class';
__PACKAGE__->load_components("Core");
__PACKAGE__->table("Users");
__PACKAGE__->add_columns(
"username",
{ data_type => "text", is_nullable => 0, size => undef },
"fullname",
{ data_type => "text", is_nullable => 0, size => undef },
"emailaddress",
{ data_type => "text", is_nullable => 0, size => undef },
"password",
{ data_type => "text", is_nullable => 0, size => undef },
);
__PACKAGE__->set_primary_key("username");
__PACKAGE__->has_many(
"projects",
"Hydra::Schema::Projects",
{ "foreign.owner" => "self.username" },
);
__PACKAGE__->has_many(
"userroles",
"Hydra::Schema::UserRoles",
{ "foreign.username" => "self.username" },
);
# Created by DBIx::Class::Schema::Loader v0.04005 @ 2009-03-04 14:50:30
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:z8fRKy//Mx8wqymMgOcrWA
# You can replace this text with custom content, and it will be preserved on regeneration
1;

View File

@ -0,0 +1,24 @@
package Hydra::View::NixClosure;
use strict;
use base qw/Catalyst::View/;
use IO::Pipe;
sub process {
my ($self, $c) = @_;
$c->response->content_type('application/x-nix-export');
my @storePaths = @{$c->stash->{storePaths}};
open(OUTPUT, "nix-store --export `nix-store -qR @storePaths` | gzip |");
my $fh = new IO::Handle;
$fh->fdopen(fileno(OUTPUT), "r") or die;
$c->response->body($fh);
return 1;
}
1;

View File

@ -0,0 +1,24 @@
package Hydra::View::NixDepGraph;
use strict;
use base qw/Catalyst::View/;
use IO::Pipe;
sub process {
my ($self, $c) = @_;
$c->response->content_type('image/png');
my @storePaths = @{$c->stash->{storePaths}};
open(OUTPUT, "nix-store --query --graph @storePaths | dot -Tpng -Gbgcolor=transparent |");
my $fh = new IO::Handle;
$fh->fdopen(fileno(OUTPUT), "r") or die;
$c->response->body($fh);
return 1;
}
1;

View File

@ -0,0 +1,59 @@
package Hydra::View::NixExprs;
use strict;
use base qw/Catalyst::View/;
use Hydra::Helper::Nix;
use Archive::Tar;
use IO::Compress::Bzip2 qw(bzip2);
sub escape {
my ($s) = @_;
$s =~ s|\\|\\\\|g;
$s =~ s|\"|\\\"|g;
$s =~ s|\$|\\\$|g;
return "\"" . $s . "\"";
}
sub process {
my ($self, $c) = @_;
my $res = "[\n";
foreach my $name (keys %{$c->stash->{nixPkgs}}) {
my $build = $c->stash->{nixPkgs}->{$name};
$res .= " # $name\n";
$res .= " { type = \"derivation\";\n";
$res .= " name = " . escape ($build->resultInfo->releasename or $build->nixname) . ";\n";
$res .= " system = " . (escape $build->system) . ";\n";
$res .= " outPath = " . $build->outpath . ";\n";
$res .= " meta = {\n";
$res .= " description = " . (escape $build->description) . ";\n"
if $build->description;
$res .= " longDescription = " . (escape $build->longdescription) . ";\n"
if $build->longdescription;
$res .= " license = " . (escape $build->license) . ";\n"
if $build->license;
$res .= " };\n";
$res .= " }\n";
}
$res .= "]\n";
my $tar = Archive::Tar->new;
$tar->add_data("channel/channel-name", ($c->stash->{channelName} or "unnamed-channel"), {mtime => 0});
$tar->add_data("channel/default.nix", $res, {mtime => 0});
my $tardata = $tar->write;
my $bzip2data;
bzip2(\$tardata => \$bzip2data);
$c->response->content_type('application/x-bzip2');
$c->response->body($bzip2data);
return 1;
}
1;

View File

@ -0,0 +1,43 @@
package Hydra::View::NixManifest;
use strict;
use base qw/Catalyst::View/;
use Hydra::Helper::Nix;
sub process {
my ($self, $c) = @_;
my @storePaths = @{$c->stash->{storePaths}};
$c->response->content_type('text/x-nix-manifest');
my @paths = split '\n', `nix-store --query --requisites @storePaths`;
die "cannot query dependencies of path(s) @storePaths: $?" if $? != 0;
my $manifest =
"version {\n" .
" ManifestVersion: 4\n" .
"}\n";
foreach my $path (@paths) {
my ($hash, $deriver, $refs) = queryPathInfo $path;
my $url = $c->stash->{narBase} . $path;
$manifest .=
"{\n" .
" StorePath: $path\n" .
(scalar @{$refs} > 0 ? " References: @{$refs}\n" : "") .
(defined $deriver ? " Deriver: $deriver\n" : "") .
" NarURL: $url\n" .
" NarHash: $hash\n" .
"}\n";
}
$c->response->body($manifest);
return 1;
}
1;

View File

@ -0,0 +1,25 @@
package Hydra::View::NixNAR;
use strict;
use base qw/Catalyst::View/;
sub process {
my ($self, $c) = @_;
my $storePath = $c->stash->{storePath};
$c->response->content_type('application/x-nix-archive'); # !!! check MIME type
open(OUTPUT, "nix-store --dump '$storePath' | bzip2 |");
my $fh = new IO::Handle;
$fh->fdopen(fileno(OUTPUT), "r") or die;
$c->response->body($fh);
undef $fh;
return 1;
}
1;

View File

@ -0,0 +1,22 @@
package Hydra::View::NixPkg;
use strict;
use base qw/Catalyst::View/;
sub process {
my ($self, $c) = @_;
$c->response->content_type('application/nix-package');
my $build = $c->stash->{build};
my $s = "NIXPKG1 " . $c->stash->{manifestUri}
. " " . $build->nixname . " " . $build->system
. " " . $build->drvpath . " " . $build->outpath;
$c->response->body($s);
return 1;
}
1;

8
src/lib/Hydra/View/TT.pm Normal file
View File

@ -0,0 +1,8 @@
package Hydra::View::TT;
use strict;
use base 'Catalyst::View::TT';
__PACKAGE__->config(TEMPLATE_EXTENSION => '.tt');
1;