diff --git a/flake.nix b/flake.nix index b93ad4aa..5d76568a 100644 --- a/flake.nix +++ b/flake.nix @@ -429,7 +429,6 @@ CatalystAuthenticationStoreDBIxClass CatalystAuthenticationStoreLDAP CatalystDevel - CatalystDispatchTypeRegex CatalystPluginAccessLog CatalystPluginAuthorizationRoles CatalystPluginCaptcha diff --git a/src/lib/Hydra.pm b/src/lib/Hydra.pm index 6d85f3a9..07aec922 100644 --- a/src/lib/Hydra.pm +++ b/src/lib/Hydra.pm @@ -8,7 +8,6 @@ use Hydra::Plugin; use Hydra::Model::DB; use Catalyst::Runtime '5.70'; use Catalyst qw/ConfigLoader - Unicode::Encoding Static::Simple StackTrace Authentication diff --git a/src/lib/Hydra/Controller/Root.pm b/src/lib/Hydra/Controller/Root.pm index df178df4..01bb7b44 100644 --- a/src/lib/Hydra/Controller/Root.pm +++ b/src/lib/Hydra/Controller/Root.pm @@ -15,6 +15,9 @@ use JSON; use List::Util qw[min max]; use List::MoreUtils qw{any}; use Net::Prometheus; +use Types::Standard qw/StrMatch/; + +use constant NARINFO_REGEX => qr{^([a-z0-9]{32})\.narinfo$}; # Put this controller at top-level. __PACKAGE__->config->{namespace} = ''; @@ -349,17 +352,17 @@ sub nix_cache_info :Path('nix-cache-info') :Args(0) { } -sub narinfo :LocalRegex('^([a-z0-9]+).narinfo$') :Args(0) { - my ($self, $c) = @_; +sub narinfo :Path :Args(StrMatch[NARINFO_REGEX]) { + my ($self, $c, $narinfo) = @_; if (!isLocalStore) { notFound($c, "There is no binary cache here."); } else { - my $hash = $c->req->captures->[0]; + my ($hash) = $narinfo =~ NARINFO_REGEX; - die if length($hash) != 32; + die("Hash length was not 32") if length($hash) != 32; my $path = queryPathFromHashPart($hash); if (!$path) { diff --git a/src/lib/Hydra/Event.pm b/src/lib/Hydra/Event.pm index e3954308..b14d74f4 100644 --- a/src/lib/Hydra/Event.pm +++ b/src/lib/Hydra/Event.pm @@ -17,7 +17,7 @@ sub parse_payload :prototype($$) { my ($channel_name, $payload) = @_; my @payload = split /\t/, $payload; - my $parser = %channels_to_events{$channel_name}; + my $parser = $channels_to_events{$channel_name}; unless (defined $parser) { die "Invalid channel name: '$channel_name'"; } diff --git a/src/lib/Hydra/Helper/CatalystUtils.pm b/src/lib/Hydra/Helper/CatalystUtils.pm index 0b48e455..64e3c44f 100644 --- a/src/lib/Hydra/Helper/CatalystUtils.pm +++ b/src/lib/Hydra/Helper/CatalystUtils.pm @@ -279,7 +279,7 @@ sub requirePost { sub trim { - my $s = shift; + my $s = shift // ""; $s =~ s/^\s+|\s+$//g; return $s; } diff --git a/src/script/hydra-eval-jobset b/src/script/hydra-eval-jobset index 7a9af09f..ad757771 100755 --- a/src/script/hydra-eval-jobset +++ b/src/script/hydra-eval-jobset @@ -644,7 +644,7 @@ sub checkJobsetWrapped { # Hash the arguments to hydra-eval-jobs and check the # JobsetInputHashes to see if the previous evaluation had the same # inputs. If so, bail out. - my @args = ($jobset->nixexprinput, $jobset->nixexprpath, inputsToArgs($inputInfo)); + my @args = ($jobset->nixexprinput // "", $jobset->nixexprpath // "", inputsToArgs($inputInfo)); my $argsHash = sha256_hex("@args"); my $prevEval = getPrevJobsetEval($db, $jobset, 0); if (defined $prevEval && $prevEval->hash eq $argsHash && !$dryRun && !$jobset->forceeval && $prevEval->flake eq $flakeRef) { diff --git a/t/Controller/Root/narinfo.t b/t/Controller/Root/narinfo.t new file mode 100644 index 00000000..297d336b --- /dev/null +++ b/t/Controller/Root/narinfo.t @@ -0,0 +1,48 @@ +use strict; +use warnings; +use Setup; +use Data::Dumper; +use JSON qw(decode_json); +my %ctx = test_init( + # Without this, the test will fail because a `file:` store is not treated as a + # local store by `isLocalStore` in src/lib/Hydra/Helper/Nix.pm, and any + # requests to /HASH.narinfo will fail. + use_external_destination_store => 0 +); + +require Hydra::Schema; +require Hydra::Model::DB; +require Hydra::Helper::Nix; + +use Test2::V0; +require Catalyst::Test; +use HTTP::Request::Common; +Catalyst::Test->import('Hydra'); + +my $db = Hydra::Model::DB->new; +hydra_setup($db); + +my $project = $db->resultset('Projects')->create({name => "tests", displayname => "", owner => "root"}); + +my $jobset = createBaseJobset("basic", "basic.nix", $ctx{jobsdir}); + +ok(evalSucceeds($jobset), "Evaluating jobs/basic.nix should exit with return code 0"); +for my $build (queuedBuildsForJobset($jobset)) { + ok(runBuild($build), "Build '".$build->job."' from jobs/basic.nix should exit with return code 0"); +} + +subtest "/HASH.narinfo" => sub { + my $build_redirect = request(GET '/job/tests/basic/empty_dir/latest-finished'); + my $url = URI->new($build_redirect->header('location'))->path; + my $json = request(GET $url, Accept => 'application/json'); + my $data = decode_json($json->content); + my $outpath = $data->{buildoutputs}{out}{path}; + my ($hash) = $outpath =~ qr{/nix/store/([a-z0-9]{32}).*}; + my $narinfo_response = request(GET "/$hash.narinfo"); + ok($narinfo_response->is_success, "Getting the narinfo of a build"); + + my ($storepath) = $narinfo_response->content =~ qr{StorePath: (.*)}; + is($storepath, $outpath, "The returned store path is the same as the out path") +}; + +done_testing; diff --git a/t/Helper/CatalystUtils.t b/t/Helper/CatalystUtils.t new file mode 100644 index 00000000..26f8dcde --- /dev/null +++ b/t/Helper/CatalystUtils.t @@ -0,0 +1,28 @@ +use strict; +use warnings; +use Setup; +use Test2::V0; +use Hydra::Helper::CatalystUtils; + +subtest "trim" => sub { + my %values = ( + "" => "", + "šŸŒ®" => 'šŸŒ®', + " šŸŒ®" => 'šŸŒ®', + "šŸŒ® " => 'šŸŒ®', + " šŸŒ® " => 'šŸŒ®', + "\nšŸŒ® " => 'šŸŒ®', + "\n\tšŸŒ®\n\n\t" => 'šŸŒ®', + ); + + for my $input (keys %values) { + my $value = $values{$input}; + is(trim($input), $value, "Trim the value: " . $input); + } + + my $uninitialized; + + is(trim($uninitialized), '', "Trimming an uninitialized value"); +}; + +done_testing;