module, since Controller/Build.pm needs it to create a new build.
OOQ2D3KCLFPYNAN253PHWLBQMB6OMO2KYQWQXLTP65SQAYZWQ5LAC Q24QXGSMSBGB32S45SNOJO2ZDXEDRZQ2OGYVDVOH4I3QCAWSSIAQC WRIU3S5EO3RB3IM5PUDNHLOOMUPD5UKWNUL4YMAKD3C6O4KELJCAC LBNVQXUBEZ45SOTGVXK5UEZXIAIZTJLWZNUYFI4JZ6J65N3KPDVQC ODNCGFQ5FPKFI624BVMLW7PJ2EFJOR3TY66OCZM42UNNTWBCF2TQC 3HZY24CX4U2TO74HOY4YX3LBJIYF4DLXHCIY7J2RASAC4COMSMZAC D5QIOJGPKQJIYBUCSC3MFJ3TXLPNZ2XMI37GXMFRVRFWWR2VMTFAC FPK5LF53CFUEKFYJ3IYXT4UTVC6IITWJOCFATMC4PLHEUP5SIEAAC AS2OXLRMJGRI64FIEM4T7EV24NZYIMPPR2EQN3SR5A2JBHVXNYXAC CS7T2XFIISZ7TPQ6FFRIDYPR6BDCQHURP4JE4YKLI4OYSNWKZCHAC IN272KZWHENW2TCR3LWQ6OZAEESJL5S7AEL3GYLJTWHJUDE6HADAC 2GK5DOU7ODF4WBSN3QTD3WIO52VTL2LOAXKGCDEMMAQPTEO4A4HAC AFTXA575C6JTVLVXTYJUKQGPLBO3NFORLO5XDSPHNL44HXLRH4TAC H7CNGK4OJNRYZQGPLBGR72DULLEPFQ5UISF5J24D7IMA7SYW5LGQC N22GPKYTOLZLBGTGDATQDVZ4R5APZEAOIA7L32X4UXBH4XNI7MWAC 6BLUKEQ2M5RGWMPXPYIFIEVEUBV4PYAZ75S2WSBIATMRGYFMQZHQC TLZ2SPBRX274EUS73SUUCOFYQUXB76S3F4AOSJXDYXIMMS7JIHEAC X27GNHDV5KPZ5GSH6DCAJMNCEMZLCP7M43JWF2X3O5QWXMOX273AC NI5BVF2VLMDA7REXTV455SUCQAYMKPKNKQSRII2SJJGG2TCX4CTAC POPU2ATH2HHBTGHKRAV3EY2K55P664IARI3YJGLDKVJ6PQPXBQ4AC 7ZHHVD6QVZCR7OHF3OIS52DF7ZIQDIS27OXCKOGMFDO2CTIOGI5AC M552HLIAP52D42AVXVC5SGROAYN2TBCEUZOXESWEMBBUX7G3U6TAC HJLYC7537T37E5AQ4Y4PYDBRLVIZVWYS4R7WPSLGO6SJPS6M6LMAC ECBA3GQOGTF73Y7A5EFUXZ5PDIZ5NPJM3WMOUJTE3AEK2PZQX3MQC LZO3C2KIACZ3HN72RBGWWIT5ED4RJMYKI3SAHXT6RIUPHDFL3STQC NLJJZVHO3UXBURL2P7VGGCVUOMKFUYT3UX5JXQU3FFFAHUGEKO3AC PHX2HIVGHHKCAX6VNN2WXD4LRGSA74KQMJCCTMHK7HS6JPELVECAC ZVTSOVHNQNQCRF3N44RKDQSL3UM7HSLTAXICMWEE6EIA6SWJXZCQC EBUKGUD55EQBCGZHUN7USJH5UHARIMLIZE7A2BFWNK3AV7WYNALQC F2G7Z2XKW6JYJBMOIRD33RF2ATPVDA7MIXMXH3YZOTRP2PZ6KARAC YAPITGB3ENS6PXRBFC647ESCQUYG442DKHUPM46PJKCKTMHWGVXAC my $jobName = trim $c->request->params->{"jobname"};error($c, "Invalid job name: $jobName") if $jobName !~ /^$jobNameRE$/;foreach my $param (keys %{$c->request->params}) {next unless $param =~ /^input-(\w+)-name$/;my $baseName = $1;my ($inputName, $inputType) =Hydra::Controller::Jobset::checkInput($c, $baseName);my $inputValue = Hydra::Controller::Jobset::checkInputValue($c, $inputType, $c->request->params->{"input-$baseName-value"});eval {fetchInput($c->model('DB'), $build->project, $build->jobset,$inputName, $inputType, $inputValue);};error($c, $@) if $@;}
}sub nixExprPathFromParams {my ($c) = @_;# The Nix expression path must be relative and can't contain ".." elements.my $nixExprPath = trim $c->request->params->{"nixexprpath"};error($c, "Invalid Nix expression path: $nixExprPath") if $nixExprPath !~ /^$relPathRE$/;my $nixExprInput = trim $c->request->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 "cvs" || $inputType eq "tarball" ||$inputType eq "string" || $inputType eq "path" || $inputType eq "boolean" ||$inputType eq "build";return ($inputName, $inputType);
# The Nix expression path must be relative and can't contain ".." elements.my $nixExprPath = trim $c->request->params->{"nixexprpath"};error($c, "Invalid Nix expression path: $nixExprPath") if $nixExprPath !~ /^$relPathRE$/;my $nixExprInput = trim $c->request->params->{"nixexprinput"};error($c, "Invalid Nix expression input name: $nixExprInput") unless $nixExprInput =~ /^\w+$/;
my ($nixExprPath, $nixExprInput) = nixExprPathFromParams $c;
my $inputType = trim $c->request->params->{"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" ||$inputType eq "build";
package Hydra::Helper::AddBuilds;use strict;use POSIX qw(strftime);use Hydra::Helper::Nix;our @ISA = qw(Exporter);our @EXPORT = qw(fetchInput);sub getStorePathHash {my ($storePath) = @_;my $hash = `nix-store --query --hash $storePath`or die "cannot get hash of $storePath";chomp $hash;die unless $hash =~ /^sha256:(.*)$/;$hash = $1;$hash = `nix-hash --to-base16 --type sha256 $hash`or die "cannot convert hash";chomp $hash;return $hash;}sub parseJobName {# Parse a job specification of the form `<project>:<jobset>:<job># [attrs]'. The project, jobset and attrs may be omitted. The# attrs have the form `name = "value"'.my ($s) = @_;our $key;our %attrs = ();# hm, maybe I should stop programming Perl before it's too late...$s =~ / ^ (?: (?: ([\w\-]+) : )? ([\w\-]+) : )? ([\w\-]+) \s*(\[ \s* (([\w]+) (?{ $key = $^N; }) \s* = \s* \"([\w\-]+) (?{ $attrs{$key} = $^N; }) \"\s* )* \])? $/xor die "invalid job specifier `$s'";return ($1, $2, $3, \%attrs);}sub attrsToSQL {my ($attrs, $id) = @_;my $query = "1 = 1";foreach my $name (keys %{$attrs}) {my $value = $attrs->{$name};$name =~ /^[\w\-]+$/ or die;$value =~ /^[\w\-]+$/ or die;# !!! 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 exists (select 1 from buildinputs where build = $id and name = '$name' and value = '$value')";}return $query;}sub fetchInput {my ($db, $project, $jobset, $name, $type, $value) = @_;if ($type eq "path") {my $uri = $value;my $timestamp = time;my $sha256;my $storePath;# Some simple caching: don't check a path more than once every N seconds.(my $cachedInput) = $db->resultset('CachedPathInputs')->search({srcpath => $uri, lastseen => {">", $timestamp - 60}},{rows => 1, order_by => "lastseen DESC"});if (defined $cachedInput && isValidPath($cachedInput->storepath)) {$storePath = $cachedInput->storepath;$sha256 = $cachedInput->sha256hash;$timestamp = $cachedInput->timestamp;} else {print STDERR "copying input ", $name, " from $uri\n";$storePath = `nix-store --add "$uri"`or die "Cannot copy path $uri to the Nix store.\n";chomp $storePath;$sha256 = getStorePathHash $storePath;($cachedInput) = $db->resultset('CachedPathInputs')->search({srcpath => $uri, sha256hash => $sha256});# Path inputs don't have a natural notion of a "revision",# so we simulate it by using the timestamp that we first# saw this path have this SHA-256 hash. So if the# contents of the path changes, we get a new "revision",# but if it doesn't change (or changes back), we don't get# a new "revision".if (!defined $cachedInput) {txn_do($db, sub {$db->resultset('CachedPathInputs')->create({ srcpath => $uri, timestamp => $timestamp, lastseen => $timestamp, sha256hash => $sha256, storepath => $storePath});});} else {$timestamp = $cachedInput->timestamp;txn_do($db, sub {$cachedInput->update({lastseen => time});});}}return{ type => $type, uri => $uri, storePath => $storePath, sha256hash => $sha256, revision => strftime "%Y%m%d%H%M%S", gmtime($timestamp)};}elsif ($type eq "svn") {my $uri = $value;my $sha256;my $storePath;# First figure out the last-modified revision of the URI.my @cmd = (["svn", "ls", "-v", "--depth", "empty", $uri],"|", ["sed", 's/^ *\([0-9]*\).*/\1/']);my $stdout; my $stderr;die "Cannot get head revision of Subversion repository at `$uri':\n$stderr"unless IPC::Run::run(@cmd, \$stdout, \$stderr);my $revision = $stdout; chomp $revision;die unless $revision =~ /^\d+$/;(my $cachedInput) = $db->resultset('CachedSubversionInputs')->search({uri => $uri, revision => $revision});if (defined $cachedInput && isValidPath($cachedInput->storepath)) {$storePath = $cachedInput->storepath;$sha256 = $cachedInput->sha256hash;} else {# Then download this revision into the store.print STDERR "checking out Subversion input ", $name, " from $uri revision $revision\n";$ENV{"NIX_HASH_ALGO"} = "sha256";$ENV{"PRINT_PATH"} = "1";(my $res, $stdout, $stderr) = captureStdoutStderr("nix-prefetch-svn", $uri, $revision);die "Cannot check out Subversion repository `$uri':\n$stderr" unless $res;($sha256, $storePath) = split ' ', $stdout;txn_do($db, sub {$db->resultset('CachedSubversionInputs')->create({ uri => $uri, revision => $revision, sha256hash => $sha256, storepath => $storePath});});}return{ type => $type, uri => $uri, storePath => $storePath, sha256hash => $sha256, revision => $revision};}elsif ($type eq "build") {my ($projectName, $jobsetName, $jobName, $attrs) = parseJobName($value);$projectName ||= $project->name;$jobsetName ||= $jobset->name;# Pick the most recent successful build of the specified job.(my $prevBuild) = $db->resultset('Builds')->search({ finished => 1, project => $projectName, jobset => $jobsetName, job => $jobName, buildStatus => 0 },{ join => 'resultInfo', order_by => "me.id DESC", rows => 1, where => \ attrsToSQL($attrs, "me.id") });if (!defined $prevBuild || !isValidPath($prevBuild->outpath)) {print STDERR "input `", $name, "': no previous build available\n";return undef;}#print STDERR "input `", $name, "': using build ", $prevBuild->id, "\n";my $pkgNameRE = "(?:(?:[A-Za-z0-9]|(?:-[^0-9]))+)";my $versionRE = "(?:[A-Za-z0-9\.\-]+)";my $relName = ($prevBuild->resultInfo->releasename or $prevBuild->nixname);my $version = $2 if $relName =~ /^($pkgNameRE)-($versionRE)$/;return{ type => "build", storePath => $prevBuild->outpath, id => $prevBuild->id, version => $version};}elsif ($type eq "string") {die unless defined $value;return {type => $type, value => $value};}elsif ($type eq "boolean") {die unless defined $value && ($value eq "true" || $value eq "false");return {type => $type, value => $value};}else {die "Input `" . $name . "' has unknown type `$type'.";}}
Readonly::Scalar our $relPathRE => "(?:$pathCompRE(?:\/$pathCompRE)*)";Readonly::Scalar our $relNameRE =>"(?:[A-Za-z0-9-][A-Za-z0-9-\.]*)";
Readonly::Scalar our $relPathRE => "(?:$pathCompRE(?:/$pathCompRE)*)";Readonly::Scalar our $relNameRE => "(?:[A-Za-z0-9-][A-Za-z0-9-\.]*)";Readonly::Scalar our $attrNameRE => "(?:[A-Za-z_][A-Za-z0-9_]*)";Readonly::Scalar our $jobNameRE => "(?:$attrNameRE(?:\\.$attrNameRE)*)";
sub getStorePathHash {my ($storePath) = @_;my $hash = `nix-store --query --hash $storePath`or die "cannot get hash of $storePath";chomp $hash;die unless $hash =~ /^sha256:(.*)$/;$hash = $1;$hash = `nix-hash --to-base16 --type sha256 $hash`or die "cannot convert hash";chomp $hash;return $hash;}sub parseJobName {# Parse a job specification of the form `<project>:<jobset>:<job># [attrs]'. The project, jobset and attrs may be omitted. The# attrs have the form `name = "value"'.my ($s) = @_;our $key;our %attrs = ();# hm, maybe I should stop programming Perl before it's too late...$s =~ / ^ (?: (?: ([\w\-]+) : )? ([\w\-]+) : )? ([\w\-]+) \s*(\[ \s* (([\w]+) (?{ $key = $^N; }) \s* = \s* \"([\w\-]+) (?{ $attrs{$key} = $^N; }) \"\s* )* \])? $/xor die "invalid job specifier `$s'";return ($1, $2, $3, \%attrs);}sub attrsToSQL {my ($attrs, $id) = @_;my $query = "1 = 1";foreach my $name (keys %{$attrs}) {my $value = $attrs->{$name};$name =~ /^[\w\-]+$/ or die;$value =~ /^[\w\-]+$/ or die;# !!! 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 exists (select 1 from buildinputs where build = $id and name = '$name' and value = '$value')";}return $query;}sub fetchInputAlt {my ($project, $jobset, $input, $alt) = @_;my $type = $input->type;if ($type eq "path") {my $uri = $alt->value;my $timestamp = time;my $sha256;my $storePath;# Some simple caching: don't check a path more than once every N seconds.(my $cachedInput) = $db->resultset('CachedPathInputs')->search({srcpath => $uri, lastseen => {">", $timestamp - 60}},{rows => 1, order_by => "lastseen DESC"});if (defined $cachedInput && isValidPath($cachedInput->storepath)) {$storePath = $cachedInput->storepath;$sha256 = $cachedInput->sha256hash;$timestamp = $cachedInput->timestamp;} else {print "copying input ", $input->name, " from $uri\n";$storePath = `nix-store --add "$uri"`or die "cannot copy path $uri to the Nix store";chomp $storePath;$sha256 = getStorePathHash $storePath;($cachedInput) = $db->resultset('CachedPathInputs')->search({srcpath => $uri, sha256hash => $sha256});# Path inputs don't have a natural notion of a "revision",# so we simulate it by using the timestamp that we first# saw this path have this SHA-256 hash. So if the# contents of the path changes, we get a new "revision",# but if it doesn't change (or changes back), we don't get# a new "revision".if (!defined $cachedInput) {txn_do($db, sub {$db->resultset('CachedPathInputs')->create({ srcpath => $uri, timestamp => $timestamp, lastseen => $timestamp, sha256hash => $sha256, storepath => $storePath});});} else {$timestamp = $cachedInput->timestamp;txn_do($db, sub {$cachedInput->update({lastseen => time});});}}return{ type => $type, uri => $uri, storePath => $storePath, sha256hash => $sha256, revision => strftime "%Y%m%d%H%M%S", gmtime($timestamp)};}elsif ($type eq "svn") {my $uri = $alt->value;my $sha256;my $storePath;# First figure out the last-modified revision of the URI.my @cmd = (["svn", "ls", "-v", "--depth", "empty", $uri],"|", ["sed", 's/^ *\([0-9]*\).*/\1/']);my $stdout; my $stderr;die "cannot get head revision of Subversion repository at `$uri':\n$stderr"unless IPC::Run::run(@cmd, \$stdout, \$stderr);my $revision = $stdout; chomp $revision;die unless $revision =~ /^\d+$/;(my $cachedInput) = $db->resultset('CachedSubversionInputs')->search({uri => $uri, revision => $revision});if (defined $cachedInput && isValidPath($cachedInput->storepath)) {$storePath = $cachedInput->storepath;$sha256 = $cachedInput->sha256hash;} else {# Then download this revision into the store.print "checking out Subversion input ", $input->name, " from $uri revision $revision\n";$ENV{"NIX_HASH_ALGO"} = "sha256";$ENV{"PRINT_PATH"} = "1";(my $res, $stdout, $stderr) = captureStdoutStderr("nix-prefetch-svn", $uri, $revision);die "cannot check out Subversion repository `$uri':\n$stderr" unless $res;($sha256, $storePath) = split ' ', $stdout;txn_do($db, sub {$db->resultset('CachedSubversionInputs')->create({ uri => $uri, revision => $revision, sha256hash => $sha256, storepath => $storePath});});}return{ type => $type, uri => $uri, storePath => $storePath, sha256hash => $sha256, revision => $revision};}elsif ($type eq "build") {my ($projectName, $jobsetName, $jobName, $attrs) = parseJobName($alt->value);$projectName ||= $project->name;$jobsetName ||= $jobset->name;# Pick the most recent successful build of the specified job.(my $prevBuild) = $db->resultset('Builds')->search({ finished => 1, project => $projectName, jobset => $jobsetName, job => $jobName, buildStatus => 0 },{ join => 'resultInfo', order_by => "me.id DESC", rows => 1, where => \ attrsToSQL($attrs, "me.id") });if (!defined $prevBuild || !isValidPath($prevBuild->outpath)) {print STDERR "input `", $input->name, "': no previous build available\n";return undef;}#print STDERR "input `", $input->name, "': using build ", $prevBuild->id, "\n";my $pkgNameRE = "(?:(?:[A-Za-z0-9]|(?:-[^0-9]))+)";my $versionRE = "(?:[A-Za-z0-9\.\-]+)";my $relName = ($prevBuild->resultInfo->releasename or $prevBuild->nixname);my $version = $2 if $relName =~ /^($pkgNameRE)-($versionRE)$/;return{ type => "build", storePath => $prevBuild->outpath, id => $prevBuild->id, version => $version};}elsif ($type eq "string") {die unless defined $alt->value;return {type => $type, value => $alt->value};}elsif ($type eq "boolean") {die unless defined $alt->value && ($alt->value eq "true" || $alt->value eq "false");return {type => $type, value => $alt->value};}else {die "input `" . $input->name . "' has unknown type `$type'";}}